diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 2bb64c657..8975dcb49 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -10,7 +10,7 @@ body: attributes: label: Checklist options: - - label: I have added `battery_notes:` to my configuration.yaml and restarted. + - label: I have restarted Home Assistant after the HACS or manual install. required: true - label: I have read the [FAQ's](https://andrew-codechimp.github.io/HA-Battery-Notes/faq). required: true diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..b091f1530 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,140 @@ +# Battery Notes - AI Coding Instructions + +## Project Overview +Battery Notes is a Home Assistant custom integration that tracks battery information for IoT devices. It uses a **sub-entry architecture** where a single config entry can manage multiple battery devices via ConfigSubentry objects. + +## Architecture & Core Components + +### Config Entry Structure (Critical) +- **Main Config Entry**: Domain-level configuration (`BatteryNotesConfigEntry`) +- **Sub-entries**: Individual device configurations (`ConfigSubentry`) stored in `config_entry.runtime_data.subentries` +- **Coordinators**: Each sub-entry has its own `BatteryNotesSubentryCoordinator` in `config_entry.runtime_data.subentry_coordinators[subentry_id]` + +**Key Pattern**: Always iterate through `subentry_coordinators.values()` not the main coordinator when looking for devices. + +### Entity Inheritance Structure +- `BatteryNotesEntity` (base class in `entity.py`) - handles device association and common setup +- Sensor classes inherit from both `BatteryNotesEntity` and HA sensor classes +- Example: `class BatteryNotesTypeSensor(BatteryNotesEntity, RestoreSensor)` + +### Device Discovery & Library +- `library/library.json`: 10K+ device definitions with battery types +- `discovery.py`: Auto-discovers devices and creates config entries +- Device matching: `manufacturer` + `model` (+ optional `hw_version`) + +## Key File Responsibilities + +### Core Files +- `__init__.py`: Config entry setup, sub-entry management, platform loading +- `coordinator.py`: Data coordination, battery level tracking, event firing +- `config_flow.py`: UI configuration flows for manual device addition +- `services.py`: Battery replacement service handlers +- `entity.py`: Base entity class with device association logic + +### Platform Files +- `sensor.py`: Battery type, battery+, and last replaced sensors +- `binary_sensor.py`: Battery low binary sensors +- `button.py`: Battery replaced action buttons + +### Supporting Files +- `library.py`: Device library management and updates +- `discovery.py`: Automatic device discovery from library +- `const.py`: All constants, service schemas, event names +- `store.py`: Persistent storage for user data + +## Development Workflow + +### Local Development +```bash +# Start HA development server on port 8123 +./scripts/develop + +# Lint code +./scripts/lint + +# Check types (if mypy installed) +mypy custom_components/battery_notes/ --check-untyped-defs +``` + +### File Structure Conventions +- Put constants in `const.py` with proper categorization +- Use dataclasses for configuration objects +- Service schemas defined in `const.py`, handlers in `services.py` +- All entities extend `BatteryNotesEntity` base class + +## Critical Patterns + +### Sub-entry Iteration Pattern +```python +# CORRECT - iterate through sub-entry coordinators +for config_entry in hass.config_entries.async_loaded_entries(DOMAIN): + if not config_entry.runtime_data.subentry_coordinators: + continue + for coordinator in config_entry.runtime_data.subentry_coordinators.values(): + if coordinator.device_id == target_device_id: + # Process device +``` + +### Entity Registration Pattern +```python +# Use unique_id from coordinator + description suffix +self._attr_unique_id = f"{coordinator.device_id}_{description.unique_id_suffix}" + +# Handle entity naming based on device association +if coordinator.source_entity_id and not coordinator.device_id: + self._attr_translation_placeholders = {"device_name": coordinator.device_name + " "} +``` + +### Service Implementation Pattern +```python +# Always validate device exists before processing +device_found = False +for coordinator in subentry_coordinators.values(): + if coordinator.device_id == device_id: + device_found = True + # Process service call + break + +if not device_found: + _LOGGER.error("Device %s not configured in Battery Notes", device_id) + return None +``` + +## Testing & Validation + +### Type Checking +- Project uses mypy with `--check-untyped-defs` +- All major classes use type hints +- ConfigEntry typing: `BatteryNotesConfigEntry = ConfigEntry[BatteryNotesData]` + +### Linting +- Uses `ruff` for code formatting and linting +- Config in `pyproject.toml` +- Run `./scripts/lint` before commits + +## Integration Points + +### Home Assistant APIs +- Device Registry: Link entities to HA devices +- Entity Registry: Manage entity lifecycle +- Event Bus: Fire battery events (`EVENT_BATTERY_THRESHOLD`, `EVENT_BATTERY_REPLACED`) +- Config Flow: Handle UI configuration + +### External Dependencies +- Library updates from `https://battery-notes-data.codechimp.org/library.json` +- Version checking with `awesomeversion` +- Data validation with `voluptuous` + +## Common Tasks + +### Adding New Entity Types +1. Create entity description in appropriate platform file +2. Extend `BatteryNotesEntity` base class +3. Add to platform's `async_setup_entry` function +4. Register constants in `const.py` + +### Adding New Services +1. Define schema in `const.py` +2. Implement handler in `services.py` using sub-entry iteration pattern +3. Register in `__init__.py` `async_setup_entry` +4. Add service definition to `services.yaml` diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 9d6ad5277..627dc2b13 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -30,8 +30,7 @@ jobs: with: args: check --output-format=github - #TODO: Temporary disable until V3 - # - name: Ruff Format - # uses: astral-sh/ruff-action@v3 - # with: - # args: format --check --diff + - name: Ruff Format + uses: astral-sh/ruff-action@v3 + with: + args: format --check --diff diff --git a/.gitignore b/.gitignore index 9eb1a48e6..3bc5d361d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,5 @@ config/* !config/configuration.yaml .DS_Store config/configuration.yaml - +backup/* custom_components/battery_notes/frontend/node_modules diff --git a/README.md b/README.md index 7ff0dc5b3..a03b0fbb2 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,6 @@ _If you want to show your support please_ ## Installation -**Important** - -Once you have installed battery notes using either HACS or manually as per the instructions below you must add the following entry to your `configuration.yaml`, then restart Home Assistant. This will enable discovery of your devices. - -``` -battery_notes: -``` - -If you need to add a device that is not automatically discovered from the library go to Settings -> Integrations click "+" and search for "Battery Notes" - ### HACS Installation [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=andrew-codechimp&repository=HA-Battery-Notes&category=Integration) @@ -46,6 +36,12 @@ If you need to add a device that is not automatically discovered from the librar Or Search for `Battery Notes` in HACS and install it under the "Integrations" category. +Restart Home Assistant + +In the HA UI go to Settings -> Integrations click "+ Add integration" and search for "Battery Notes" + +This will create the main Battery Notes integration service and discovery will start shortly afterwards, or you can battery notes manually within the integration. + ### Manual Installation
@@ -53,14 +49,9 @@ Search for `Battery Notes` in HACS and install it under the "Integrations" categ - You should take the battery_notes.zip file from the latest [published release](https://github.com/andrew-codechimp/ha-battery-notes/releases). - To install, place the contents of `custom_components` into the `/custom_components` folder of your Home Assistant installation. -- Add the following entry to your `configuration.yaml` - -``` -battery_notes: -``` - - Restart Home Assistant -- In the HA UI go to Settings -> Integrations click "+" and search for "Battery Notes" +- In the HA UI go to Settings -> Integrations click "+ Add integration" and search for "Battery Notes" +- This will create the main Battery Notes integration service and discovery will start shortly afterwards, or you can battery notes manually within the integration.
## Docs diff --git a/custom_components/battery_notes/__init__.py b/custom_components/battery_notes/__init__.py index 86ab8796d..58b8343c9 100644 --- a/custom_components/battery_notes/__init__.py +++ b/custom_components/battery_notes/__init__.py @@ -6,56 +6,76 @@ from __future__ import annotations -import logging import re +import logging +from types import MappingProxyType +from datetime import datetime import voluptuous as vol from awesomeversion.awesomeversion import AwesomeVersion -from homeassistant.const import __version__ as HA_VERSION # noqa: N812 -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers import entity_registry as er -from homeassistant.helpers import issue_registry as ir + +from homeassistant.core import HassJob, HomeAssistant, callback +from homeassistant.const import ( + CONF_DEVICE_ID, + __version__ as HA_VERSION, # noqa: N812 +) +from homeassistant.helpers import ( + issue_registry as ir, + device_registry as dr, + entity_registry as er, + config_validation as cv, +) +from homeassistant.helpers.event import async_call_later +from homeassistant.config_entries import SOURCE_IGNORE, ConfigEntry, ConfigSubentry +from homeassistant.helpers.device import async_entity_id_to_device_id from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue +from homeassistant.helpers.helper_integration import ( + async_remove_helper_config_entry_from_source_device, +) -from .config_flow import CONFIG_VERSION from .const import ( - CONF_BATTERY_INCREASE_THRESHOLD, - CONF_BATTERY_QUANTITY, + NAME as INTEGRATION_NAME, + DOMAIN, + PLATFORMS, + CONF_MODEL, + CONF_MODEL_ID, + MIN_HA_VERSION, + CONF_HW_VERSION, + CONF_DEVICE_NAME, CONF_BATTERY_TYPE, - CONF_DEFAULT_BATTERY_LOW_THRESHOLD, - CONF_ENABLE_AUTODISCOVERY, - CONF_ENABLE_REPLACED, CONF_HIDE_BATTERY, - CONF_LIBRARY_URL, + CONF_MANUFACTURER, + CONF_USER_LIBRARY, CONF_ROUND_BATTERY, - CONF_SCHEMA_URL, + CONF_ENABLE_REPLACED, + CONF_BATTERY_QUANTITY, CONF_SHOW_ALL_DEVICES, - CONF_USER_LIBRARY, - DEFAULT_BATTERY_INCREASE_THRESHOLD, + ISSUE_DEPRECATED_YAML, + SUBENTRY_BATTERY_NOTE, + CONF_ADVANCED_SETTINGS, + CONF_ENABLE_AUTODISCOVERY, DEFAULT_BATTERY_LOW_THRESHOLD, - DEFAULT_LIBRARY_URL, - DEFAULT_SCHEMA_URL, - DOMAIN, - MIN_HA_VERSION, - PLATFORMS, + CONF_BATTERY_INCREASE_THRESHOLD, + CONF_DEFAULT_BATTERY_LOW_THRESHOLD, + DEFAULT_BATTERY_INCREASE_THRESHOLD, ) +from .store import async_get_registry +from .library import DATA_LIBRARY, Library +from .services import async_setup_services +from .discovery import DiscoveryManager from .coordinator import ( MY_KEY, - BatteryNotesConfigEntry, - BatteryNotesCoordinator, BatteryNotesData, + BatteryNotesConfigEntry, BatteryNotesDomainConfig, + BatteryNotesSubentryCoordinator, ) -from .discovery import DiscoveryManager from .library_updater import LibraryUpdater -from .services import async_setup_services -from .store import ( - async_get_registry, -) _LOGGER = logging.getLogger(__name__) +DISCOVERY_DELAY = 10 CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.All( @@ -75,14 +95,6 @@ CONF_BATTERY_INCREASE_THRESHOLD, default=DEFAULT_BATTERY_INCREASE_THRESHOLD, ): cv.positive_int, - vol.Optional( - CONF_LIBRARY_URL, - default=DEFAULT_LIBRARY_URL, - ): cv.string, - vol.Optional( - CONF_SCHEMA_URL, - default=DEFAULT_SCHEMA_URL, - ): cv.string, }, ), ), @@ -103,39 +115,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.critical(msg) return False + await async_migrate_integration(hass, config) + store = await async_get_registry(hass) domain_config = BatteryNotesDomainConfig( - enable_autodiscovery=config.get(DOMAIN, {}).get( - CONF_ENABLE_AUTODISCOVERY, True - ), - show_all_devices=config.get(DOMAIN, {}).get(CONF_SHOW_ALL_DEVICES, False), - enable_replaced=config.get(DOMAIN, {}).get(CONF_ENABLE_REPLACED, True), - hide_battery=config.get(DOMAIN, {}).get(CONF_HIDE_BATTERY, False), - round_battery=config.get(DOMAIN, {}).get(CONF_ROUND_BATTERY, False), - default_battery_low_threshold=config.get(DOMAIN, {}).get( - CONF_DEFAULT_BATTERY_LOW_THRESHOLD, DEFAULT_BATTERY_LOW_THRESHOLD - ), - battery_increased_threshod=config.get(DOMAIN, {}).get( - CONF_BATTERY_INCREASE_THRESHOLD, DEFAULT_BATTERY_INCREASE_THRESHOLD - ), - library_url=config.get(DOMAIN, {}).get(CONF_LIBRARY_URL, DEFAULT_LIBRARY_URL), - schema_url=config.get(DOMAIN, {}).get(CONF_SCHEMA_URL, DEFAULT_SCHEMA_URL), - user_library=config.get(DOMAIN, {}).get(CONF_USER_LIBRARY, ""), store=store, ) hass.data[MY_KEY] = domain_config - - library_updater = LibraryUpdater(hass) - await library_updater.copy_schema() - await library_updater.get_library_updates(startup=True) - - if domain_config.enable_autodiscovery: - discovery_manager = DiscoveryManager(hass, domain_config) - await discovery_manager.start_discovery() - else: - _LOGGER.debug("Auto discovery disabled") + hass.data[DATA_LIBRARY] = Library(hass) # Register custom services async_setup_services(hass) @@ -148,19 +137,73 @@ async def async_setup_entry( ) -> bool: """Set up a config entry.""" - data = hass.data[MY_KEY] - assert data.store + domain_config = hass.data[MY_KEY] + assert domain_config.store + + domain_config.show_all_devices = config_entry.options[CONF_SHOW_ALL_DEVICES] + domain_config.hide_battery = config_entry.options[CONF_HIDE_BATTERY] + domain_config.round_battery = config_entry.options[CONF_ROUND_BATTERY] + domain_config.default_battery_low_threshold = config_entry.options[ + CONF_DEFAULT_BATTERY_LOW_THRESHOLD + ] + domain_config.battery_increased_threshod = config_entry.options[ + CONF_BATTERY_INCREASE_THRESHOLD + ] + + domain_config.enable_autodiscovery = config_entry.options[CONF_ADVANCED_SETTINGS][ + CONF_ENABLE_AUTODISCOVERY + ] + domain_config.enable_replaced = config_entry.options[CONF_ADVANCED_SETTINGS][ + CONF_ENABLE_REPLACED + ] + domain_config.user_library = config_entry.options[CONF_ADVANCED_SETTINGS][ + CONF_USER_LIBRARY + ] + config_entry.runtime_data = BatteryNotesData( - domain_config=data, - store=data.store, + domain_config=domain_config, + store=domain_config.store, + loaded_subentries=config_entry.subentries.copy(), ) - coordinator = BatteryNotesCoordinator(hass, config_entry) - config_entry.runtime_data.coordinator = coordinator + discovery_manager = DiscoveryManager(hass, domain_config) + + config_entry.runtime_data.subentry_coordinators = {} + for subentry in config_entry.subentries.values(): + if subentry.subentry_type == SUBENTRY_BATTERY_NOTE: + coordinator = BatteryNotesSubentryCoordinator(hass, config_entry, subentry) + config_entry.runtime_data.subentry_coordinators[subentry.subentry_id] = ( + coordinator + ) + + assert subentry.unique_id await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS) - config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) + config_entry.async_on_unload( + config_entry.add_update_listener(_async_update_listener) + ) + + @callback + async def _async_delayed_discovery(now: datetime) -> None: # noqa: ARG001 + """Update the library and do discovery.""" + library_updater = LibraryUpdater(hass) + await library_updater.copy_schema() + await library_updater.get_library_updates(startup=True) + + if domain_config.enable_autodiscovery: + await discovery_manager.start_discovery() + else: + _LOGGER.debug("Auto discovery disabled") + + # Let the system settle a bit before starting discovery + async_call_later( + hass, + DISCOVERY_DELAY, + HassJob( + _async_delayed_discovery, "battery notes discovery", cancel_on_shutdown=True + ), + ) return True @@ -176,94 +219,320 @@ async def async_unload_entry( async def async_remove_entry( hass: HomeAssistant, config_entry: BatteryNotesConfigEntry ) -> None: - """Device removed, tidy up store.""" + """Battery Notes integration removed.""" - # Remove any issues raised - ir.async_delete_issue(hass, DOMAIN, f"missing_device_{config_entry.entry_id}") + for subentry in config_entry.subentries.values(): + if subentry.subentry_id not in config_entry.subentries: + await _async_remove_subentry( + hass, config_entry, subentry, remove_store_entries=False + ) - store = await async_get_registry(hass) - coordinator = BatteryNotesCoordinator(hass, config_entry) - if coordinator.source_entity_id: - store.async_delete_entity(coordinator.source_entity_id) - else: - if coordinator.device_id: - store.async_delete_device(coordinator.device_id) +async def async_migrate_integration(hass: HomeAssistant, config: ConfigType) -> None: # noqa: PLR0912, PLR0915 + """Migrate integration entry structure.""" + + migrate_base_entry: ConfigEntry | None = None + + yaml_domain_config = config.get(DOMAIN) + if yaml_domain_config is not None: + async_create_issue( + hass, + DOMAIN, + ISSUE_DEPRECATED_YAML, + is_fixable=False, + issue_domain=DOMAIN, + severity=IssueSeverity.WARNING, + translation_key=ISSUE_DEPRECATED_YAML, + translation_placeholders={ + "domain": DOMAIN, + "integration_title": INTEGRATION_NAME, + }, + ) - _LOGGER.debug("Removed battery note %s", config_entry.entry_id) + entries = sorted( + hass.config_entries.async_entries(DOMAIN), + key=lambda e: e.disabled_by is not None, + ) - # Unhide the battery - entity_registry = er.async_get(hass) - if not coordinator.wrapped_battery: + if not any( + entry.version < 3 and entry.source != SOURCE_IGNORE for entry in entries + ): return - if not ( - wrapped_battery_entity_entry := entity_registry.async_get( - coordinator.wrapped_battery.entity_id + for entry in entries: + if entry.version == 3 and entry.unique_id == DOMAIN: + # We have a V3 entry, so we can use this as the base + migrate_base_entry = entry + break + + for entry in entries: + if entry.version >= 3: + continue + + if entry.source == SOURCE_IGNORE: + continue + + if entry.disabled_by is not None: + # We cannot migrate a disabled entry to a subentry, delete it instead + await hass.config_entries.async_remove(entry.entry_id) + continue + + # Tidy up data we accidentally added from discovery + entry_data_dict = dict(entry.data) + entry_data_dict.pop(CONF_DEVICE_NAME, None) + entry_data_dict.pop(CONF_MANUFACTURER, None) + entry_data_dict.pop(CONF_MODEL, None) + entry_data_dict.pop(CONF_MODEL_ID, None) + entry_data_dict.pop(CONF_HW_VERSION, None) + + subentry = ConfigSubentry( + data=MappingProxyType(entry_data_dict), + subentry_type=SUBENTRY_BATTERY_NOTE, + title=entry.title, + unique_id=entry.unique_id, ) - ): - return - if wrapped_battery_entity_entry.hidden_by == er.RegistryEntryHider.INTEGRATION: - entity_registry.async_update_entity( - coordinator.wrapped_battery.entity_id, hidden_by=None + if not migrate_base_entry: + if yaml_domain_config: + options = { + CONF_SHOW_ALL_DEVICES: yaml_domain_config.get( + CONF_SHOW_ALL_DEVICES, False + ), + CONF_HIDE_BATTERY: yaml_domain_config.get(CONF_HIDE_BATTERY, False), + CONF_ROUND_BATTERY: yaml_domain_config.get( + CONF_ROUND_BATTERY, False + ), + CONF_DEFAULT_BATTERY_LOW_THRESHOLD: yaml_domain_config.get( + CONF_DEFAULT_BATTERY_LOW_THRESHOLD, + DEFAULT_BATTERY_LOW_THRESHOLD, + ), + CONF_BATTERY_INCREASE_THRESHOLD: yaml_domain_config.get( + CONF_BATTERY_INCREASE_THRESHOLD, + DEFAULT_BATTERY_INCREASE_THRESHOLD, + ), + CONF_ADVANCED_SETTINGS: { + CONF_ENABLE_AUTODISCOVERY: yaml_domain_config.get( + CONF_ENABLE_AUTODISCOVERY, True + ), + CONF_ENABLE_REPLACED: yaml_domain_config.get( + CONF_ENABLE_REPLACED, True + ), + CONF_USER_LIBRARY: yaml_domain_config.get( + CONF_USER_LIBRARY, "" + ), + }, + } + else: + options = { + CONF_SHOW_ALL_DEVICES: False, + CONF_HIDE_BATTERY: False, + CONF_ROUND_BATTERY: False, + CONF_DEFAULT_BATTERY_LOW_THRESHOLD: DEFAULT_BATTERY_LOW_THRESHOLD, + CONF_BATTERY_INCREASE_THRESHOLD: DEFAULT_BATTERY_INCREASE_THRESHOLD, + CONF_ADVANCED_SETTINGS: { + CONF_ENABLE_AUTODISCOVERY: True, + CONF_ENABLE_REPLACED: True, + CONF_USER_LIBRARY: "", + }, + } + + hass.config_entries.async_update_entry( + entry, + version=3, + title=INTEGRATION_NAME, + data={}, + options=options, + unique_id=DOMAIN, + ) + + migrate_base_entry = entry + + hass.config_entries.async_add_subentry(migrate_base_entry, subentry) + + # Update entities unique_id to be the subentry + entity_registry = er.async_get(hass) + for entity_entry in entity_registry.entities.values(): + if entity_entry.config_entry_id == entry.entry_id: + # Update the unique_id to be the subentry unique_id + + if "_" not in entity_entry.unique_id: + new_unique_id = f"{subentry.unique_id}_battery_type" + else: + new_unique_id = f"{subentry.unique_id}_{entity_entry.unique_id.split('_', 1)[1]}" + + entity_disabled_by = entity_entry.disabled_by + if entity_disabled_by: + entity_disabled_by = er.RegistryEntryDisabler.USER + + entity_registry.async_update_entity( + entity_entry.entity_id, + config_entry_id=migrate_base_entry.entry_id, + config_subentry_id=subentry.subentry_id, + disabled_by=entity_disabled_by, + new_unique_id=new_unique_id, + ) + + # Remove the config entry from the device + source_device_id = subentry.data.get(CONF_DEVICE_ID, None) + device_registry = dr.async_get(hass) + source_device = device_registry.async_get(source_device_id) + if source_device is None: + source_device_id = None + + source_entity_id = subentry.data.get("source_entity_id", None) + if source_entity_id: + source_device_id = async_entity_id_to_device_id(hass, source_entity_id) + + if source_device_id: + async_remove_helper_config_entry_from_source_device( + hass=hass, + helper_config_entry_id=entry.entry_id, + source_device_id=source_device_id, + ) + + # Remove the old config entry + if entry.entry_id != migrate_base_entry.entry_id: + await hass.config_entries.async_remove(entry.entry_id) + + _LOGGER.info( + "Entry %s successfully migrated to subentry of %s.", + entry.entry_id, + migrate_base_entry.entry_id, ) - _LOGGER.debug("Unhidden Original Battery for device%s", coordinator.device_id) async def async_migrate_entry( hass: HomeAssistant, config_entry: BatteryNotesConfigEntry -): +) -> bool: """Migrate old config.""" - new_version = CONFIG_VERSION + _LOGGER.debug( + "Migrating configuration from version %s.%s", + config_entry.version, + config_entry.minor_version, + ) + + if config_entry.version > 3: + # This means the user has downgraded from a future version + return False if config_entry.version == 1: # Version 1 had a single config for qty & type, split them - _LOGGER.debug("Migrating config entry from version %s", config_entry.version) - matches = re.search( r"^(\d+)(?=x)(?:x\s)(\w+$)|([\s\S]+)", config_entry.data[CONF_BATTERY_TYPE] ) if matches: - _qty = matches.group(1) if matches.group(1) is not None else "1" - _type = ( + battery_qty = matches.group(1) if matches.group(1) is not None else "1" + battery_type = ( matches.group(2) if matches.group(2) is not None else matches.group(3) ) else: - _qty = 1 - _type = config_entry.data[CONF_BATTERY_TYPE] + battery_qty = 1 + battery_type = config_entry.data[CONF_BATTERY_TYPE] new_data = {**config_entry.data} - new_data[CONF_BATTERY_TYPE] = _type - new_data[CONF_BATTERY_QUANTITY] = _qty + new_data[CONF_BATTERY_TYPE] = battery_type + new_data[CONF_BATTERY_QUANTITY] = battery_qty hass.config_entries.async_update_entry( - config_entry, version=new_version, title=config_entry.title, data=new_data + config_entry, version=2, title=config_entry.title, data=new_data ) _LOGGER.info( "Entry %s successfully migrated to version %s.", config_entry.entry_id, - new_version, + 2, + ) + + if config_entry.version == 2 and config_entry.source == SOURCE_IGNORE: + hass.config_entries.async_update_entry( + config_entry, version=3, title=config_entry.title ) return True -async def update_listener( +async def _async_update_listener( hass: HomeAssistant, config_entry: BatteryNotesConfigEntry ) -> None: """Update the device and related entities. - Triggered when the device is renamed on the frontend. + Triggered when the device is renamed on the frontend, or when sub entries are updated. + + Look at sub entries and remove any that are no longer present. """ + for subentry in config_entry.runtime_data.loaded_subentries.values(): + if subentry.subentry_id not in config_entry.subentries: + await _async_remove_subentry( + hass, config_entry, subentry, remove_store_entries=False + ) + + # Update the config entry with the new sub entries + config_entry.runtime_data.loaded_subentries = config_entry.subentries.copy() + await hass.config_entries.async_reload(config_entry.entry_id) -@callback -async def async_update_options( - hass: HomeAssistant, entry: BatteryNotesConfigEntry +async def _async_remove_subentry( + hass: HomeAssistant, + config_entry: BatteryNotesConfigEntry, + subentry: ConfigSubentry, + remove_store_entries: bool, ) -> None: - """Update options.""" - await hass.config_entries.async_reload(entry.entry_id) + """Remove a sub entry.""" + _LOGGER.debug( + "Removing sub entry %s from config entry %s", + subentry.subentry_id, + config_entry.entry_id, + ) + + assert config_entry.runtime_data.subentry_coordinators + coordinator = config_entry.runtime_data.subentry_coordinators.get( + subentry.subentry_id + ) + assert coordinator + + # Remove any issues raised + ir.async_delete_issue(hass, DOMAIN, f"missing_device_{subentry.subentry_id}") + + # Remove store entries + if remove_store_entries: + store = coordinator.config_entry.runtime_data.store + + if coordinator.source_entity_id: + store.async_delete_entity(coordinator.source_entity_id) + elif coordinator.device_id: + store.async_delete_device(coordinator.device_id) + + # Unhide the battery + if coordinator.wrapped_battery: + entity_registry = er.async_get(hass) + if wrapped_battery_entity_entry := entity_registry.async_get( + coordinator.wrapped_battery.entity_id + ): + if ( + wrapped_battery_entity_entry.hidden_by + == er.RegistryEntryHider.INTEGRATION + ): + entity_registry.async_update_entity( + coordinator.wrapped_battery.entity_id, hidden_by=None + ) + _LOGGER.debug( + "Unhidden Original Battery for device%s", coordinator.device_id + ) + + config_entry.runtime_data.subentry_coordinators.pop(subentry.subentry_id) + + entity_registry = er.async_get(hass) + entities_to_remove = [ + entity_entry.entity_id + for entity_entry in entity_registry.entities.values() + if entity_entry.config_entry_id == config_entry.entry_id + and entity_entry.config_subentry_id == subentry.subentry_id + ] + + for entity_id in entities_to_remove: + entity_registry.async_remove(entity_id) + _LOGGER.debug( + "Removed entity %s for subentry %s", entity_id, subentry.subentry_id + ) diff --git a/custom_components/battery_notes/binary_sensor.py b/custom_components/battery_notes/binary_sensor.py index e78168141..73ab91490 100644 --- a/custom_components/battery_notes/binary_sensor.py +++ b/custom_components/battery_notes/binary_sensor.py @@ -3,87 +3,76 @@ from __future__ import annotations import logging -from collections.abc import Callable, Mapping -from dataclasses import dataclass from typing import Any +from dataclasses import dataclass +from collections.abc import Mapping, Callable import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, - BinarySensorDeviceClass, - BinarySensorEntity, - BinarySensorEntityDescription, -) -from homeassistant.const import ( - CONF_DEVICE_ID, - CONF_NAME, - STATE_UNAVAILABLE, - STATE_UNKNOWN, -) + from homeassistant.core import ( Event, HomeAssistant, callback, split_entity_id, ) -from homeassistant.exceptions import TemplateError -from homeassistant.helpers import ( - config_validation as cv, +from homeassistant.const import ( + CONF_NAME, + STATE_UNKNOWN, + CONF_DEVICE_ID, + STATE_UNAVAILABLE, ) from homeassistant.helpers import ( + template, device_registry as dr, -) -from homeassistant.helpers import ( entity_registry as er, + config_validation as cv, ) -from homeassistant.helpers import ( - template, -) -from homeassistant.helpers.entity import DeviceInfo, Entity, EntityCategory -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import ( - EVENT_ENTITY_REGISTRY_UPDATED, -) +from homeassistant.exceptions import TemplateError from homeassistant.helpers.event import ( - EventStateChangedData, TrackTemplate, TrackTemplateResult, - async_track_entity_registry_updated_event, - async_track_state_change_event, + EventStateChangedData, + TrackTemplateResultInfo, async_track_template_result, + async_track_state_change_event, ) -from homeassistant.helpers.reload import async_setup_reload_service -from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.start import async_at_start +from homeassistant.helpers.entity import Entity, EntityCategory from homeassistant.helpers.template import ( Template, TemplateStateFromEntityId, ) -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, +from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.entity_registry import ( + EVENT_ENTITY_REGISTRY_UPDATED, +) +from homeassistant.components.binary_sensor import ( + PLATFORM_SCHEMA, + BinarySensorEntity, + BinarySensorDeviceClass, + BinarySensorEntityDescription, ) -from . import PLATFORMS -from .common import validate_is_float from .const import ( - ATTR_BATTERY_LAST_REPLACED, - ATTR_BATTERY_LOW_THRESHOLD, - ATTR_BATTERY_QUANTITY, - ATTR_BATTERY_TYPE, - ATTR_BATTERY_TYPE_AND_QUANTITY, + DOMAIN, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, + ATTR_BATTERY_TYPE, + ATTR_BATTERY_QUANTITY, ATTR_SOURCE_ENTITY_ID, CONF_SOURCE_ENTITY_ID, - DOMAIN, + SUBENTRY_BATTERY_NOTE, + ATTR_BATTERY_LAST_REPLACED, + ATTR_BATTERY_LOW_THRESHOLD, + ATTR_BATTERY_TYPE_AND_QUANTITY, ) +from .common import validate_is_float +from .entity import BatteryNotesEntity, BatteryNotesEntityDescription from .coordinator import ( MY_KEY, BatteryNotesConfigEntry, - BatteryNotesCoordinator, -) -from .entity import ( - BatteryNotesEntityDescription, + BatteryNotesSubentryCoordinator, ) _LOGGER = logging.getLogger(__name__) @@ -94,7 +83,7 @@ class BatteryNotesBinarySensorEntityDescription( BatteryNotesEntityDescription, BinarySensorEntityDescription, ): - """Describes Battery Notes button entity.""" + """Describes Battery Notes binary sensor entity.""" unique_id_suffix: str @@ -109,7 +98,9 @@ class BatteryNotesBinarySensorEntityDescription( @callback -def async_add_to_device(hass: HomeAssistant, entry: BatteryNotesConfigEntry) -> str | None: +def async_add_to_device( + hass: HomeAssistant, entry: BatteryNotesConfigEntry +) -> str | None: """Add our config entry to the device.""" device_registry = dr.async_get(hass) @@ -127,121 +118,88 @@ def async_add_to_device(hass: HomeAssistant, entry: BatteryNotesConfigEntry) -> async def async_setup_entry( hass: HomeAssistant, config_entry: BatteryNotesConfigEntry, - async_add_entities: AddEntitiesCallback, + async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Initialize Battery Type config entry.""" - entity_registry = er.async_get(hass) - device_registry = dr.async_get(hass) - device_id = config_entry.data.get(CONF_DEVICE_ID) + for subentry in config_entry.subentries.values(): + if subentry.subentry_type != SUBENTRY_BATTERY_NOTE: + continue - async def async_registry_updated( - event: Event[er.EventEntityRegistryUpdatedData], - ) -> None: - """Handle entity registry update.""" - data = event.data - if data["action"] == "remove": - await hass.config_entries.async_remove(config_entry.entry_id) - - if data["action"] != "update": - return - - if "entity_id" in data["changes"]: - # Entity_id changed, reload the config entry - await hass.config_entries.async_reload(config_entry.entry_id) - - if device_id and "device_id" in data["changes"]: - # If the tracked battery note is no longer in the device, remove our config entry - # from the device - if ( - not (entity_entry := entity_registry.async_get(data["entity_id"])) - or not device_registry.async_get(device_id) - or entity_entry.device_id == device_id - ): - # No need to do any cleanup - return + assert config_entry.runtime_data.subentry_coordinators + coordinator = config_entry.runtime_data.subentry_coordinators.get( + subentry.subentry_id + ) + assert coordinator - device_registry.async_update_device( - device_id, remove_config_entry_id=config_entry.entry_id + if coordinator.is_orphaned: + _LOGGER.debug( + "Skipping binary_sensor creation for orphaned subentry: %s", + subentry.title, ) - - coordinator = config_entry.runtime_data.coordinator - assert(coordinator) - - config_entry.async_on_unload( - async_track_entity_registry_updated_event( - hass, config_entry.entry_id, async_registry_updated + continue + + battery_low_entity_description = BatteryNotesBinarySensorEntityDescription( + unique_id_suffix="_battery_low", + key="_battery_plus_low", + translation_key="battery_low", + entity_category=EntityCategory.DIAGNOSTIC, + device_class=BinarySensorDeviceClass.BATTERY, + entity_type="binary_sensor", ) - ) - - if not coordinator.fake_device: - device_id = async_add_to_device(hass, config_entry) - - if not device_id: - return - description = BatteryNotesBinarySensorEntityDescription( - unique_id_suffix="_battery_low", - key="_battery_plus_low", - translation_key="battery_low", - entity_category=EntityCategory.DIAGNOSTIC, - device_class=BinarySensorDeviceClass.BATTERY, - ) + entities: list[ + BatteryNotesBatteryLowTemplateSensor + | BatteryNotesBatteryLowSensor + | BatteryNotesBatteryBinaryLowSensor + ] = [] - if coordinator.battery_low_template is not None: - async_add_entities( - [ + if coordinator.battery_low_template is not None: + entities.append( BatteryNotesBatteryLowTemplateSensor( hass, coordinator, - description, - f"{config_entry.entry_id}{description.unique_id_suffix}", + battery_low_entity_description, + f"{subentry.unique_id}{battery_low_entity_description.unique_id_suffix}", coordinator.battery_low_template, ) - ] - ) + ) - elif coordinator.wrapped_battery is not None: - async_add_entities( - [ + elif coordinator.wrapped_battery is not None: + entities.append( BatteryNotesBatteryLowSensor( hass, coordinator, - description, - f"{config_entry.entry_id}{description.unique_id_suffix}", + battery_low_entity_description, + f"{subentry.unique_id}{battery_low_entity_description.unique_id_suffix}", ) - ] - ) + ) - elif coordinator.wrapped_battery_low is not None: - async_add_entities( - [ + elif coordinator.wrapped_battery_low is not None: + entities.append( BatteryNotesBatteryBinaryLowSensor( hass, coordinator, - description, - f"{config_entry.entry_id}{description.unique_id_suffix}", + battery_low_entity_description, + f"{subentry.unique_id}{battery_low_entity_description.unique_id_suffix}", ) - ] - ) - - -async def async_setup_platform( - hass: HomeAssistant, -) -> None: - """Set up the battery note sensor.""" + ) - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) + if entities: + async_add_entities( + entities, + config_subentry_id=subentry.subentry_id, + ) class _TemplateAttribute: """Attribute value linked to template result.""" - def __init__( + def __init__( # noqa: PLR0913 self, entity: Entity, attribute: str, - template: Template, + tmpl: Template, validator: Callable[[Any], Any] | None = None, on_update: Callable[[Any], None] | None = None, none_on_template_error: bool | None = False, @@ -249,7 +207,7 @@ def __init__( """Template attribute.""" self._entity = entity self._attribute = attribute - self.template = template + self.template = tmpl self.validator = validator self.on_update = on_update self.async_update = None @@ -274,11 +232,12 @@ def _default_update(self, result: str | TemplateError) -> None: @callback def handle_result( self, - event: Event[EventStateChangedData] | None, - template: Template, - last_result: str | None | TemplateError, + event: Event[EventStateChangedData] | None, # noqa: ARG002 + tmpl: Template, # noqa: ARG002 + last_result: str | None | TemplateError, # noqa: ARG002 result: str | TemplateError, ) -> None: + # pylint: disable=unused-argument """Handle a template result event callback.""" if isinstance(result, TemplateError): _LOGGER.error( @@ -329,19 +288,22 @@ def handle_result( return -class BatteryNotesBatteryLowBaseSensor( - BinarySensorEntity, CoordinatorEntity[BatteryNotesCoordinator] -): +class BatteryNotesBatteryLowBaseSensor(BatteryNotesEntity, BinarySensorEntity): """Low battery binary sensor base.""" + entity_description: BatteryNotesBinarySensorEntityDescription + def __init__( self, hass: HomeAssistant, - coordinator: BatteryNotesCoordinator, + coordinator: BatteryNotesSubentryCoordinator, + entity_description: BatteryNotesBinarySensorEntityDescription, ): """Initialize the low battery binary sensor.""" - super().__init__(coordinator=coordinator) + super().__init__( + hass, entity_description=entity_description, coordinator=coordinator + ) self.enable_replaced = hass.data[MY_KEY].enable_replaced @@ -395,54 +357,39 @@ class BatteryNotesBatteryLowTemplateSensor( def __init__( self, hass: HomeAssistant, - coordinator: BatteryNotesCoordinator, - description: BatteryNotesBinarySensorEntityDescription, + coordinator: BatteryNotesSubentryCoordinator, + entity_description: BatteryNotesBinarySensorEntityDescription, unique_id: str, - template: str, + tmpl: str, ) -> None: """Create a low battery binary sensor.""" - device_registry = dr.async_get(hass) + super().__init__( + hass=hass, coordinator=coordinator, entity_description=entity_description + ) - self.coordinator = coordinator - self.entity_description = description self._attr_unique_id = unique_id self._template_attrs: dict[Template, list[_TemplateAttribute]] = {} - - super().__init__(hass=hass, coordinator=coordinator) - - if coordinator.device_id and ( - device_entry := device_registry.async_get(coordinator.device_id) - ): - self._attr_device_info = DeviceInfo( - connections=device_entry.connections, - identifiers=device_entry.identifiers, - ) - - self._attr_has_entity_name = True + self._template_result_info: TrackTemplateResultInfo | None = None if coordinator.source_entity_id and not coordinator.device_id: self._attr_translation_placeholders = { "device_name": coordinator.device_name + " " } - self.entity_id = ( - f"binary_sensor.{coordinator.device_name.lower()}_{description.key}" - ) + self.entity_id = f"binary_sensor.{coordinator.device_name.lower()}_{entity_description.key}" elif coordinator.source_entity_id and coordinator.device_id: - source_entity_domain, source_object_id = split_entity_id( - coordinator.source_entity_id - ) + _, source_object_id = split_entity_id(coordinator.source_entity_id) self._attr_translation_placeholders = { "device_name": coordinator.source_entity_name + " " } - self.entity_id = f"binary_sensor.{source_object_id}_{description.key}" - else: - self._attr_translation_placeholders = {"device_name": ""} self.entity_id = ( - f"binary_sensor.{coordinator.device_name.lower()}_{description.key}" + f"binary_sensor.{source_object_id}_{entity_description.key}" ) + else: + self._attr_translation_placeholders = {"device_name": ""} + self.entity_id = f"binary_sensor.{coordinator.device_name.lower()}_{entity_description.key}" - self._template = template + self._template = tmpl self._state: bool | None = None async def async_added_to_hass(self) -> None: @@ -457,7 +404,7 @@ async def async_added_to_hass(self) -> None: def add_template_attribute( self, attribute: str, - template: Template, + tmpl: Template, validator: Callable[[Any], Any] | None = None, on_update: Callable[[Any], None] | None = None, none_on_template_error: bool = False, @@ -469,7 +416,7 @@ def add_template_attribute( attribute The name of the attribute to link to. This attribute must exist unless a custom on_update method is supplied. - template + tmpl The template to calculate. validator Validator function to parse the result and ensure it's valid. @@ -484,10 +431,10 @@ def add_template_attribute( assert self.hass is not None, "hass cannot be None" template.hass = self.hass template_attribute = _TemplateAttribute( - self, attribute, template, validator, on_update, none_on_template_error + self, attribute, tmpl, validator, on_update, none_on_template_error ) - self._template_attrs.setdefault(template, []) - self._template_attrs[template].append(template_attribute) + self._template_attrs.setdefault(tmpl, []) + self._template_attrs[tmpl].append(template_attribute) @callback def _async_setup_templates(self) -> None: @@ -512,7 +459,7 @@ def _async_template_startup( is_availability_template = False for attribute in attributes: # pylint: disable-next=protected-access - if attribute._attribute == "_attr_available": + if attribute._attribute == "_attr_available": # noqa: SLF001 has_availability_template = True is_availability_template = True attribute.async_setup() @@ -604,51 +551,34 @@ class BatteryNotesBatteryLowSensor(BatteryNotesBatteryLowBaseSensor): def __init__( self, hass: HomeAssistant, - coordinator: BatteryNotesCoordinator, - description: BatteryNotesBinarySensorEntityDescription, + coordinator: BatteryNotesSubentryCoordinator, + entity_description: BatteryNotesBinarySensorEntityDescription, unique_id: str, ) -> None: """Create a low battery binary sensor.""" - - device_registry = dr.async_get(hass) - - self.coordinator = coordinator - self._attr_has_entity_name = True + super().__init__( + hass=hass, coordinator=coordinator, entity_description=entity_description + ) if coordinator.source_entity_id and not coordinator.device_id: self._attr_translation_placeholders = { "device_name": coordinator.device_name + " " } - self.entity_id = ( - f"binary_sensor.{coordinator.device_name.lower()}_{description.key}" - ) + self.entity_id = f"binary_sensor.{coordinator.device_name.lower()}_{entity_description.key}" elif coordinator.source_entity_id and coordinator.device_id: - source_entity_domain, source_object_id = split_entity_id( - coordinator.source_entity_id - ) + _, source_object_id = split_entity_id(coordinator.source_entity_id) self._attr_translation_placeholders = { "device_name": coordinator.source_entity_name + " " } - self.entity_id = f"binary_sensor.{source_object_id}_{description.key}" - else: - self._attr_translation_placeholders = {"device_name": ""} self.entity_id = ( - f"binary_sensor.{coordinator.device_name.lower()}_{description.key}" + f"binary_sensor.{source_object_id}_{entity_description.key}" ) + else: + self._attr_translation_placeholders = {"device_name": ""} + self.entity_id = f"binary_sensor.{coordinator.device_name.lower()}_{entity_description.key}" - self.entity_description = description self._attr_unique_id = unique_id - super().__init__(hass=hass, coordinator=coordinator) - - if coordinator.device_id and ( - device_entry := device_registry.async_get(coordinator.device_id) - ): - self._attr_device_info = DeviceInfo( - connections=device_entry.connections, - identifiers=device_entry.identifiers, - ) - async def async_added_to_hass(self) -> None: """Handle added to Hass.""" @@ -699,56 +629,41 @@ class BatteryNotesBatteryBinaryLowSensor(BatteryNotesBatteryLowBaseSensor): def __init__( self, hass: HomeAssistant, - coordinator: BatteryNotesCoordinator, - description: BatteryNotesBinarySensorEntityDescription, + coordinator: BatteryNotesSubentryCoordinator, + entity_description: BatteryNotesBinarySensorEntityDescription, unique_id: str, ) -> None: """Create a low battery binary sensor.""" - - device_registry = dr.async_get(hass) - - self.coordinator = coordinator - self._attr_has_entity_name = True + super().__init__( + hass=hass, coordinator=coordinator, entity_description=entity_description + ) if coordinator.source_entity_id and not coordinator.device_id: self._attr_translation_placeholders = { "device_name": coordinator.device_name + " " } - self.entity_id = ( - f"binary_sensor.{coordinator.device_name.lower()}_{description.key}" - ) + self.entity_id = f"binary_sensor.{coordinator.device_name.lower()}_{entity_description.key}" elif coordinator.source_entity_id and coordinator.device_id: - source_entity_domain, source_object_id = split_entity_id( - coordinator.source_entity_id - ) + _, source_object_id = split_entity_id(coordinator.source_entity_id) self._attr_translation_placeholders = { "device_name": coordinator.source_entity_name + " " } - self.entity_id = f"binary_sensor.{source_object_id}_{description.key}" - else: - self._attr_translation_placeholders = {"device_name": ""} self.entity_id = ( - f"binary_sensor.{coordinator.device_name.lower()}_{description.key}" + f"binary_sensor.{source_object_id}_{entity_description.key}" ) + else: + self._attr_translation_placeholders = {"device_name": ""} + self.entity_id = f"binary_sensor.{coordinator.device_name.lower()}_{entity_description.key}" - self.entity_description = description self._attr_unique_id = unique_id - super().__init__(hass=hass, coordinator=coordinator) - - if coordinator.device_id and ( - device_entry := device_registry.async_get(coordinator.device_id) - ): - self._attr_device_info = DeviceInfo( - connections=device_entry.connections, - identifiers=device_entry.identifiers, - ) - self._state: bool | None = None + self._wrapped_attributes: dict[str, Any] | None = None @callback async def async_state_changed_listener( - self, event: Event[EventStateChangedData] | None = None + self, + event: Event[EventStateChangedData] | None = None, # noqa: ARG002 ) -> None: # pylint: disable=unused-argument """Handle child updates.""" @@ -823,11 +738,11 @@ async def _entity_rename_listener( self.coordinator.wrapped_battery = new_wrapped_battery # Create a listener for the newly named battery entity - if self.coordinator.wrapped_battery: + if self.coordinator.wrapped_battery_low: self.async_on_remove( async_track_state_change_event( self.hass, - [self.coordinator.wrapped_battery.entity_id], + [self.coordinator.wrapped_battery_low.entity_id], self.async_state_changed_listener, ) ) @@ -901,15 +816,14 @@ async def _async_state_changed_listener( self.coordinator.wrapped_battery_low.entity_id, hidden_by=er.RegistryEntryHider.INTEGRATION, ) - else: - if ( - self.coordinator.wrapped_battery_low - and self.coordinator.wrapped_battery_low.hidden_by - == er.RegistryEntryHider.INTEGRATION - ): - registry.async_update_entity( - self.coordinator.wrapped_battery_low.entity_id, hidden_by=None - ) + elif ( + self.coordinator.wrapped_battery_low + and self.coordinator.wrapped_battery_low.hidden_by + == er.RegistryEntryHider.INTEGRATION + ): + registry.async_update_entity( + self.coordinator.wrapped_battery_low.entity_id, hidden_by=None + ) self.async_on_remove( self.coordinator.async_add_listener(self._handle_coordinator_update) diff --git a/custom_components/battery_notes/button.py b/custom_components/battery_notes/button.py index e7d523d56..2020f37ce 100644 --- a/custom_components/battery_notes/button.py +++ b/custom_components/battery_notes/button.py @@ -5,50 +5,35 @@ import logging from dataclasses import dataclass -import voluptuous as vol -from homeassistant.components.button import ( - PLATFORM_SCHEMA, - ButtonEntity, - ButtonEntityDescription, -) +from homeassistant.core import HomeAssistant, callback, split_entity_id from homeassistant.const import ( CONF_DEVICE_ID, - CONF_NAME, -) -from homeassistant.core import Event, HomeAssistant, callback, split_entity_id -from homeassistant.helpers import ( - config_validation as cv, ) from homeassistant.helpers import ( device_registry as dr, -) -from homeassistant.helpers import ( entity_registry as er, ) -from homeassistant.helpers.entity import DeviceInfo, EntityCategory -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.event import ( - async_track_entity_registry_updated_event, +from homeassistant.helpers.entity import EntityCategory +from homeassistant.components.button import ( + ButtonEntity, + ButtonEntityDescription, ) -from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback -from . import PLATFORMS -from .common import utcnow_no_timezone from .const import ( - ATTR_BATTERY_QUANTITY, - ATTR_BATTERY_TYPE, - ATTR_BATTERY_TYPE_AND_QUANTITY, + DOMAIN, ATTR_DEVICE_ID, ATTR_DEVICE_NAME, + ATTR_BATTERY_TYPE, + ATTR_BATTERY_QUANTITY, ATTR_SOURCE_ENTITY_ID, - CONF_SOURCE_ENTITY_ID, - DOMAIN, + SUBENTRY_BATTERY_NOTE, EVENT_BATTERY_REPLACED, + ATTR_BATTERY_TYPE_AND_QUANTITY, ) -from .coordinator import BatteryNotesConfigEntry, BatteryNotesCoordinator -from .entity import ( - BatteryNotesEntityDescription, -) +from .common import utcnow_no_timezone +from .entity import BatteryNotesEntity, BatteryNotesEntityDescription +from .coordinator import BatteryNotesConfigEntry, BatteryNotesSubentryCoordinator _LOGGER = logging.getLogger(__name__) @@ -63,17 +48,10 @@ class BatteryNotesButtonEntityDescription( unique_id_suffix: str -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_DEVICE_ID): cv.string, - vol.Optional(CONF_SOURCE_ENTITY_ID): cv.string, - } -) - - @callback -def async_add_to_device(hass: HomeAssistant, entry: BatteryNotesConfigEntry) -> str | None: +def async_add_to_device( + hass: HomeAssistant, entry: BatteryNotesConfigEntry +) -> str | None: """Add our config entry to the device.""" device_registry = dr.async_get(hass) @@ -91,143 +69,90 @@ def async_add_to_device(hass: HomeAssistant, entry: BatteryNotesConfigEntry) -> async def async_setup_entry( hass: HomeAssistant, config_entry: BatteryNotesConfigEntry, - async_add_entities: AddEntitiesCallback, + async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Initialize Battery Type config entry.""" - entity_registry = er.async_get(hass) - device_registry = dr.async_get(hass) - - device_id = config_entry.data.get(CONF_DEVICE_ID, None) - - async def async_registry_updated(event: Event[er.EventEntityRegistryUpdatedData]) -> None: - """Handle entity registry update.""" - data = event.data - if data["action"] == "remove": - await hass.config_entries.async_remove(config_entry.entry_id) - - if data["action"] != "update": - return - if "entity_id" in data["changes"]: - # Entity_id changed, reload the config entry - await hass.config_entries.async_reload(config_entry.entry_id) + for subentry in config_entry.subentries.values(): + if subentry.subentry_type != SUBENTRY_BATTERY_NOTE: + continue - if device_id and "device_id" in data["changes"]: - # If the tracked battery note is no longer in the device, remove our config entry - # from the device - if ( - not (entity_entry := entity_registry.async_get(data["entity_id"])) - or not device_registry.async_get(device_id) - or entity_entry.device_id == device_id - ): - # No need to do any cleanup - return - - device_registry.async_update_device( - device_id, remove_config_entry_id=config_entry.entry_id - ) - - coordinator = config_entry.runtime_data.coordinator - assert(coordinator) - - config_entry.async_on_unload( - async_track_entity_registry_updated_event( - hass, config_entry.entry_id, async_registry_updated + assert config_entry.runtime_data.subentry_coordinators + coordinator = config_entry.runtime_data.subentry_coordinators.get( + subentry.subentry_id ) - ) - - if not coordinator.fake_device: - device_id = async_add_to_device(hass, config_entry) + assert coordinator - if not device_id: - return - - description = BatteryNotesButtonEntityDescription( - unique_id_suffix="_battery_replaced_button", - key="battery_replaced", - translation_key="battery_replaced", - entity_category=EntityCategory.DIAGNOSTIC, - entity_registry_enabled_default=config_entry.runtime_data.domain_config.enable_replaced, - ) - - async_add_entities( - [ - BatteryNotesButton( - hass, - coordinator, - description, - f"{config_entry.entry_id}{description.unique_id_suffix}", - device_id, + if coordinator.is_orphaned: + _LOGGER.debug( + "Skipping button creation for orphaned entry: %s", + subentry.title, ) - ] - ) - - -async def async_setup_platform( - hass: HomeAssistant, -) -> None: - """Set up the battery note sensor.""" + continue + + description = BatteryNotesButtonEntityDescription( + unique_id_suffix="_battery_replaced_button", + key="battery_replaced", + translation_key="battery_replaced", + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=config_entry.runtime_data.domain_config.enable_replaced, + entity_type="button", + ) - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) + async_add_entities( + [ + BatteryNotesButton( + hass, + coordinator, + description, + f"{subentry.unique_id}{description.unique_id_suffix}", + ) + ], + config_subentry_id=subentry.subentry_id, + ) -class BatteryNotesButton(ButtonEntity): +class BatteryNotesButton(BatteryNotesEntity, ButtonEntity): """Represents a battery replaced button.""" _attr_should_poll = False - entity_description: BatteryNotesButtonEntityDescription def __init__( self, hass: HomeAssistant, - coordinator: BatteryNotesCoordinator, - description: BatteryNotesButtonEntityDescription, + coordinator: BatteryNotesSubentryCoordinator, + entity_description: BatteryNotesButtonEntityDescription, unique_id: str, - device_id: str, ) -> None: """Create a battery replaced button.""" - super().__init__() - - device_registry = dr.async_get(hass) - - self.coordinator = coordinator - - self._attr_has_entity_name = True + super().__init__( + hass=hass, entity_description=entity_description, coordinator=coordinator + ) if coordinator.source_entity_id and not coordinator.device_id: self._attr_translation_placeholders = { "device_name": coordinator.device_name + " " } self.entity_id = ( - f"button.{coordinator.device_name.lower()}_{description.key}" + f"button.{coordinator.device_name.lower()}_{entity_description.key}" ) elif coordinator.source_entity_id and coordinator.device_id: - source_entity_domain, source_object_id = split_entity_id( - coordinator.source_entity_id - ) + _, source_object_id = split_entity_id(coordinator.source_entity_id) self._attr_translation_placeholders = { "device_name": coordinator.source_entity_name + " " } - self.entity_id = f"button.{source_object_id}_{description.key}" + self.entity_id = f"button.{source_object_id}_{entity_description.key}" else: self._attr_translation_placeholders = {"device_name": ""} self.entity_id = ( - f"button.{coordinator.device_name.lower()}_{description.key}" + f"button.{coordinator.device_name.lower()}_{entity_description.key}" ) - self.entity_description = description self._attr_unique_id = unique_id - self._device_id = device_id self._source_entity_id = coordinator.source_entity_id - if device_id and (device := device_registry.async_get(device_id)): - self._attr_device_info = DeviceInfo( - connections=device.connections, - identifiers=device.identifiers, - ) - async def async_added_to_hass(self) -> None: """Handle added to Hass.""" registry = er.async_get(self.hass) @@ -246,8 +171,7 @@ async def async_press(self) -> None: EVENT_BATTERY_REPLACED, { ATTR_DEVICE_ID: self.coordinator.device_id or "", - ATTR_SOURCE_ENTITY_ID: self.coordinator.source_entity_id - or "", + ATTR_SOURCE_ENTITY_ID: self.coordinator.source_entity_id or "", ATTR_DEVICE_NAME: self.coordinator.device_name, ATTR_BATTERY_TYPE_AND_QUANTITY: self.coordinator.battery_type_and_quantity, ATTR_BATTERY_TYPE: self.coordinator.battery_type, diff --git a/custom_components/battery_notes/common.py b/custom_components/battery_notes/common.py index 030bf240f..a762e01e0 100644 --- a/custom_components/battery_notes/common.py +++ b/custom_components/battery_notes/common.py @@ -2,8 +2,8 @@ from datetime import datetime -from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.util import dt as dt_util +from homeassistant.helpers.device_registry import DeviceEntry def validate_is_float(num): @@ -16,11 +16,13 @@ def validate_is_float(num): return False return False + def utcnow_no_timezone() -> datetime: """Return UTC now without timezone information.""" return dt_util.utcnow().replace(tzinfo=None) + def get_device_model_id(device_entry: DeviceEntry) -> str | None: """Get the device model if available.""" return device_entry.model_id if hasattr(device_entry, "model_id") else None diff --git a/custom_components/battery_notes/config_flow.py b/custom_components/battery_notes/config_flow.py index 4b81758d6..eb75e3dde 100644 --- a/custom_components/battery_notes/config_flow.py +++ b/custom_components/battery_notes/config_flow.py @@ -2,50 +2,95 @@ from __future__ import annotations -import copy import logging +from types import MappingProxyType from typing import Any +import voluptuous as vol + import homeassistant.helpers.device_registry as dr import homeassistant.helpers.entity_registry as er -import voluptuous as vol from homeassistant import config_entries -from homeassistant.components.sensor.const import SensorDeviceClass +from homeassistant.core import callback, split_entity_id +from homeassistant.const import CONF_NAME, CONF_DEVICE_ID, Platform +from homeassistant.helpers import selector from homeassistant.config_entries import ( ConfigEntry, - ConfigFlowResult, OptionsFlow, + ConfigSubentry, + ConfigFlowResult, + ConfigSubentryFlow, + SubentryFlowResult, ) -from homeassistant.const import ( - CONF_DEVICE_ID, - CONF_NAME, - Platform, -) -from homeassistant.core import callback, split_entity_id -from homeassistant.helpers import selector from homeassistant.helpers.typing import DiscoveryInfoType +from homeassistant.data_entry_flow import section +from homeassistant.components.sensor.const import SensorDeviceClass -from .common import get_device_model_id from .const import ( - CONF_BATTERY_LOW_TEMPLATE, - CONF_BATTERY_LOW_THRESHOLD, - CONF_BATTERY_QUANTITY, - CONF_BATTERY_TYPE, - CONF_DEVICE_NAME, - CONF_FILTER_OUTLIERS, - CONF_MANUFACTURER, + NAME as INTEGRATION_NAME, + DOMAIN, CONF_MODEL, CONF_MODEL_ID, + CONF_HW_VERSION, + CONF_DEVICE_NAME, + CONF_BATTERY_TYPE, + CONF_HIDE_BATTERY, + CONF_MANUFACTURER, + CONF_USER_LIBRARY, + CONF_ROUND_BATTERY, + CONF_ENABLE_REPLACED, + CONF_FILTER_OUTLIERS, + CONF_BATTERY_QUANTITY, + CONF_SHOW_ALL_DEVICES, CONF_SOURCE_ENTITY_ID, - DOMAIN, + SUBENTRY_BATTERY_NOTE, + CONF_ADVANCED_SETTINGS, + CONF_BATTERY_LOW_TEMPLATE, + CONF_ENABLE_AUTODISCOVERY, + CONF_BATTERY_LOW_THRESHOLD, + DEFAULT_BATTERY_LOW_THRESHOLD, + CONF_BATTERY_INCREASE_THRESHOLD, + CONF_DEFAULT_BATTERY_LOW_THRESHOLD, + DEFAULT_BATTERY_INCREASE_THRESHOLD, ) +from .common import get_device_model_id +from .library import DATA_LIBRARY, ModelInfo from .coordinator import MY_KEY -from .library import Library, ModelInfo from .library_updater import LibraryUpdater _LOGGER = logging.getLogger(__name__) -CONFIG_VERSION = 2 +DOCUMENTATION_URL = "https://andrew-codechimp.github.io/HA-Battery-Notes/" + +CONFIG_VERSION = 3 + +OPTIONS_SCHEMA = vol.Schema( + { + vol.Required(CONF_SHOW_ALL_DEVICES): selector.BooleanSelector(), + vol.Required(CONF_HIDE_BATTERY): selector.BooleanSelector(), + vol.Required(CONF_ROUND_BATTERY): selector.BooleanSelector(), + vol.Required(CONF_DEFAULT_BATTERY_LOW_THRESHOLD): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=99, mode=selector.NumberSelectorMode.BOX + ), + ), + vol.Required(CONF_BATTERY_INCREASE_THRESHOLD): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=99, mode=selector.NumberSelectorMode.BOX + ), + ), + vol.Required(CONF_ADVANCED_SETTINGS): section( + vol.Schema( + { + vol.Required(CONF_ENABLE_AUTODISCOVERY): selector.BooleanSelector(), + vol.Required(CONF_ENABLE_REPLACED): selector.BooleanSelector(), + vol.Optional(CONF_USER_LIBRARY): selector.TextSelector(), + } + ), + {"collapsed": True}, + ), + } +) DEVICE_SCHEMA_ALL = vol.Schema( { @@ -112,11 +157,32 @@ class BatteryNotesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: # noqa: ARG004 # pylint: disable=unused-argument """Get the options flow for this handler.""" return OptionsFlowHandler() + @classmethod + @callback + def async_get_supported_subentry_types( + cls, + config_entry: ConfigEntry, # noqa: ARG003 + ) -> dict[str, type[ConfigSubentryFlow]]: + """Return subentries supported by this integration.""" + return { + SUBENTRY_BATTERY_NOTE: BatteryNotesSubentryFlowHandler, + } + + async def async_get_integration_entry(self) -> ConfigEntry | None: + """Return the main integration config entry, if it exists.""" + existing_entries = self.hass.config_entries.async_entries( + domain=DOMAIN, include_ignore=False, include_disabled=False + ) + for entry in existing_entries: + if entry.title == INTEGRATION_NAME: + return entry + return None + async def async_step_integration_discovery( self, discovery_info: DiscoveryInfoType, @@ -125,16 +191,47 @@ async def async_step_integration_discovery( _LOGGER.debug("Starting discovery flow: %s", discovery_info) unique_id = f"bn_{discovery_info[CONF_DEVICE_ID]}" - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() + + config_entry = await self.async_get_integration_entry() + + if not config_entry: + _LOGGER.debug("No existing single config entry found, creating new one") + + # Init defaults + options = { + CONF_SHOW_ALL_DEVICES: False, + CONF_HIDE_BATTERY: False, + CONF_ROUND_BATTERY: False, + CONF_DEFAULT_BATTERY_LOW_THRESHOLD: DEFAULT_BATTERY_LOW_THRESHOLD, + CONF_BATTERY_INCREASE_THRESHOLD: DEFAULT_BATTERY_INCREASE_THRESHOLD, + CONF_ADVANCED_SETTINGS: { + CONF_ENABLE_AUTODISCOVERY: True, + CONF_ENABLE_REPLACED: True, + CONF_USER_LIBRARY: "", + }, + } + + self.async_create_entry(title=INTEGRATION_NAME, data={}, options=options) + config_entry = await self.async_get_integration_entry() + + if not config_entry: + return self.async_abort(reason="integration_not_added") + + for existing_subentry in config_entry.subentries.values(): + if existing_subentry.unique_id == unique_id: + _LOGGER.debug("Subentry with unique_id %s already exists", unique_id) + return self.async_abort(reason="already_configured") self.context["title_placeholders"] = { "name": discovery_info[CONF_DEVICE_NAME], "manufacturer": discovery_info[CONF_MANUFACTURER], "model": discovery_info[CONF_MODEL], "model_id": discovery_info[CONF_MODEL_ID], + "hw_version": discovery_info[CONF_HW_VERSION], } + await self.async_set_unique_id(unique_id) + return await self.async_step_device(discovery_info) async def async_step_user( @@ -144,7 +241,34 @@ async def async_step_user( # pylint: disable=unused-argument """Handle a flow initialized by the user.""" - return self.async_show_menu(step_id="user", menu_options=["device", "entity"]) + if self._async_current_entries(): + _LOGGER.debug("An existing battery_notes config entry already exists") + return self.async_abort(reason="already_configured") + + if user_input is not None: + # Init defaults + options = { + CONF_SHOW_ALL_DEVICES: False, + CONF_HIDE_BATTERY: False, + CONF_ROUND_BATTERY: False, + CONF_DEFAULT_BATTERY_LOW_THRESHOLD: DEFAULT_BATTERY_LOW_THRESHOLD, + CONF_BATTERY_INCREASE_THRESHOLD: DEFAULT_BATTERY_INCREASE_THRESHOLD, + CONF_ADVANCED_SETTINGS: { + CONF_ENABLE_AUTODISCOVERY: True, + CONF_ENABLE_REPLACED: True, + CONF_USER_LIBRARY: "", + }, + } + + return self.async_create_entry( + title=INTEGRATION_NAME, data={}, options=options + ) + + self._set_confirm_only() + return self.async_show_form( + step_id="user", + description_placeholders={"documentation_url": DOCUMENTATION_URL}, + ) async def async_step_device( self, @@ -154,6 +278,260 @@ async def async_step_device( errors: dict[str, str] = {} device_battery_details = None + if user_input is not None: + self.data = user_input + + config_entry = await self.async_get_integration_entry() + + if not config_entry: + return self.async_abort(reason="integration_not_added") + + device_id = user_input[CONF_DEVICE_ID] + + library_updater = LibraryUpdater(self.hass) + if await library_updater.time_to_update_library(1): + await library_updater.get_library_updates() + + device_registry = dr.async_get(self.hass) + device_entry = device_registry.async_get(device_id) + + if device_entry and device_entry.manufacturer and device_entry.model: + _LOGGER.debug( + "Looking up device %s %s %s %s", + device_entry.manufacturer, + device_entry.model, + get_device_model_id(device_entry) or "", + device_entry.hw_version, + ) + + self.model_info = ModelInfo( + device_entry.manufacturer, + device_entry.model, + get_device_model_id(device_entry), + device_entry.hw_version, + ) + + library = self.hass.data[DATA_LIBRARY] + if not library.is_loaded: + await library.load_libraries() + + # Set defaults if not found in library + self.data[CONF_BATTERY_QUANTITY] = 1 + + device_battery_details = await library.get_device_battery_details( + self.model_info + ) + + if device_battery_details and not device_battery_details.is_manual: + _LOGGER.debug( + "Found device %s %s %s %s", + device_entry.manufacturer, + device_entry.model, + get_device_model_id(device_entry) or "", + device_entry.hw_version, + ) + self.data[CONF_BATTERY_TYPE] = device_battery_details.battery_type + + self.data[CONF_BATTERY_QUANTITY] = ( + device_battery_details.battery_quantity + ) + + return await self.async_step_battery() + + schema = DEVICE_SCHEMA + # If show_all_devices = is specified and true, don't filter + domain_config = self.hass.data.get(MY_KEY) + if domain_config and domain_config.show_all_devices: + schema = DEVICE_SCHEMA_ALL + + return self.async_show_form( + step_id="device", + data_schema=schema, + errors=errors, + last_step=False, + ) + + async def async_step_battery( # noqa: PLR0912, PLR0915 + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Second step in config flow to add the battery type.""" + errors: dict[str, str] = {} + if user_input is not None: + config_entry = await self.async_get_integration_entry() + + if not config_entry: + return self.async_abort(reason="integration_not_added") + + self.data[CONF_BATTERY_TYPE] = user_input[CONF_BATTERY_TYPE] + self.data[CONF_BATTERY_QUANTITY] = int(user_input[CONF_BATTERY_QUANTITY]) + self.data[CONF_BATTERY_LOW_THRESHOLD] = int( + user_input[CONF_BATTERY_LOW_THRESHOLD] + ) + self.data[CONF_BATTERY_LOW_TEMPLATE] = user_input.get( + CONF_BATTERY_LOW_TEMPLATE, None + ) + self.data[CONF_FILTER_OUTLIERS] = user_input.get( + CONF_FILTER_OUTLIERS, False + ) + + source_entity_id = self.data.get(CONF_SOURCE_ENTITY_ID, None) + device_id = self.data.get(CONF_DEVICE_ID, None) + + entity_entry = None + device_entry = None + + if source_entity_id: + entity_registry = er.async_get(self.hass) + entity_entry = entity_registry.async_get(source_entity_id) + _, source_object_id = split_entity_id(source_entity_id) + if entity_entry: + entity_unique_id = entity_entry.unique_id or entity_entry.entity_id + else: + entity_unique_id = source_object_id + unique_id = f"bn_{entity_unique_id}" + else: + device_registry = dr.async_get(self.hass) + assert device_id + device_entry = device_registry.async_get(device_id) + unique_id = f"bn_{device_id}" + + await self.async_set_unique_id(unique_id) + self._abort_if_unique_id_configured() + + if CONF_NAME in self.data: + title = self.data.pop(CONF_NAME) + elif source_entity_id and entity_entry: + if entity_entry.device_id: + device_registry = dr.async_get(self.hass) + device_entry = device_registry.async_get(entity_entry.device_id) + if device_entry: + title = f"{device_entry.name_by_user or device_entry.name} - {entity_entry.name or entity_entry.original_name}" + else: + title = entity_entry.name or entity_entry.original_name + else: + title = entity_entry.name or entity_entry.original_name + else: + assert device_entry + title = device_entry.name_by_user or device_entry.name + + # Remove discovery data from data + self.data.pop(CONF_DEVICE_NAME, None) + self.data.pop(CONF_MANUFACTURER, None) + self.data.pop(CONF_MODEL, None) + self.data.pop(CONF_MODEL_ID, None) + self.data.pop(CONF_HW_VERSION, None) + + subentry = ConfigSubentry( + subentry_type=SUBENTRY_BATTERY_NOTE, + data=MappingProxyType(self.data), + title=str(title), + unique_id=unique_id, + ) + self.hass.config_entries.async_add_subentry(config_entry, subentry) + + return self.async_abort(reason="created_sub_entry") + + return self.async_show_form( + step_id="battery", + description_placeholders={ + "manufacturer": self.model_info.manufacturer if self.model_info else "", + "model": self.model_info.model if self.model_info else "", + "model_id": ( + str(self.model_info.model_id or "") if self.model_info else "" + ), + "hw_version": ( + str(self.model_info.hw_version or "") if self.model_info else "" + ), + }, + data_schema=vol.Schema( + { + vol.Required( + CONF_BATTERY_TYPE, + default=self.data.get(CONF_BATTERY_TYPE), + ): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.TEXT + ), + ), + vol.Required( + CONF_BATTERY_QUANTITY, + default=int(self.data.get(CONF_BATTERY_QUANTITY, 1)), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, max=100, mode=selector.NumberSelectorMode.BOX + ), + ), + vol.Required( + CONF_BATTERY_LOW_THRESHOLD, + default=int(self.data.get(CONF_BATTERY_LOW_THRESHOLD, 0)), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=99, mode=selector.NumberSelectorMode.BOX + ), + ), + vol.Optional( + CONF_BATTERY_LOW_TEMPLATE + ): selector.TemplateSelector(), + vol.Optional( + CONF_FILTER_OUTLIERS, default=False + ): selector.BooleanSelector(), + } + ), + errors=errors, + ) + + +class OptionsFlowHandler(OptionsFlow): + """Options flow.""" + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Manage the options.""" + + if user_input is not None: + return self.async_create_entry(data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=self.add_suggested_values_to_schema( + OPTIONS_SCHEMA, + self.config_entry.options, + ), + ) + + +class BatteryNotesSubentryFlowHandler(ConfigSubentryFlow): + """Flow for managing Battery Notes subentries.""" + + data: dict[str, Any] + model_info: ModelInfo | None = None + + @property + def _is_new(self) -> bool: + """Return if this is a new subentry.""" + return self.source == "user" + + async def async_step_user( + self, + user_input: dict[str, Any] | None = None, # noqa: ARG002 + ) -> SubentryFlowResult: + """Add a subentry.""" + + return self.async_show_menu( + step_id="user", + menu_options=["device", "entity"], + description_placeholders={"documentation_url": DOCUMENTATION_URL}, + ) + + async def async_step_device( + self, + user_input: dict | None = None, + ) -> SubentryFlowResult: + """Handle a flow for a device or discovery.""" + errors: dict[str, str] = {} + device_battery_details = None + if user_input is not None: self.data = user_input @@ -182,8 +560,9 @@ async def async_step_device( device_entry.hw_version, ) - library = Library(self.hass) - await library.load_libraries() + library = self.hass.data[DATA_LIBRARY] + if not library.is_loaded: + await library.load_libraries() # Set defaults if not found in library self.data[CONF_BATTERY_QUANTITY] = 1 @@ -227,7 +606,7 @@ async def async_step_device( async def async_step_entity( self, user_input: dict | None = None, - ) -> ConfigFlowResult: + ) -> SubentryFlowResult: """Handle a flow for a device or discovery.""" errors: dict[str, str] = {} device_battery_details = None @@ -274,8 +653,9 @@ async def async_step_entity( device_entry.hw_version, ) - library = Library(self.hass) - await library.load_libraries() + library = self.hass.data[DATA_LIBRARY] + if not library.is_loaded: + await library.load_libraries() device_battery_details = ( await library.get_device_battery_details(self.model_info) @@ -316,8 +696,11 @@ async def async_step_entity( last_step=False, ) - async def async_step_manual(self, user_input: dict[str, Any] | None = None): + async def async_step_manual( + self, user_input: dict[str, Any] | None = None + ) -> SubentryFlowResult: """Second step in config flow to add the battery type.""" + # pylint: disable=unused-argument errors: dict[str, str] = {} if user_input is not None: return await self.async_step_battery() @@ -329,11 +712,12 @@ async def async_step_manual(self, user_input: dict[str, Any] | None = None): errors=errors, ) - async def async_step_battery( + async def async_step_battery( # noqa: PLR0912 self, user_input: dict[str, Any] | None = None - ) -> ConfigFlowResult: + ) -> SubentryFlowResult: """Second step in config flow to add the battery type.""" errors: dict[str, str] = {} + if user_input is not None: self.data[CONF_BATTERY_TYPE] = user_input[CONF_BATTERY_TYPE] self.data[CONF_BATTERY_QUANTITY] = int(user_input[CONF_BATTERY_QUANTITY]) @@ -343,7 +727,9 @@ async def async_step_battery( self.data[CONF_BATTERY_LOW_TEMPLATE] = user_input.get( CONF_BATTERY_LOW_TEMPLATE, None ) - self.data[CONF_FILTER_OUTLIERS] = user_input.get(CONF_FILTER_OUTLIERS, False) + self.data[CONF_FILTER_OUTLIERS] = user_input.get( + CONF_FILTER_OUTLIERS, False + ) source_entity_id = self.data.get(CONF_SOURCE_ENTITY_ID, None) device_id = self.data.get(CONF_DEVICE_ID, None) @@ -354,9 +740,7 @@ async def async_step_battery( if source_entity_id: entity_registry = er.async_get(self.hass) entity_entry = entity_registry.async_get(source_entity_id) - source_entity_domain, source_object_id = split_entity_id( - source_entity_id - ) + _, source_object_id = split_entity_id(source_entity_id) if entity_entry: entity_unique_id = entity_entry.unique_id or entity_entry.entity_id else: @@ -368,20 +752,33 @@ async def async_step_battery( device_entry = device_registry.async_get(device_id) unique_id = f"bn_{device_id}" - await self.async_set_unique_id(unique_id) - self._abort_if_unique_id_configured() + # Check if unique_id already exists + config_entry = self._get_entry() + for existing_subentry in config_entry.subentries.values(): + if existing_subentry.unique_id == unique_id: + _LOGGER.debug( + "Subentry with unique_id %s already exists", unique_id + ) + return self.async_abort(reason="already_configured") if CONF_NAME in self.data: - title = self.data.get(CONF_NAME) + title = self.data.pop(CONF_NAME) elif source_entity_id and entity_entry: - title = entity_entry.name or entity_entry.original_name + if entity_entry.device_id: + device_registry = dr.async_get(self.hass) + device_entry = device_registry.async_get(entity_entry.device_id) + if device_entry: + title = f"{device_entry.name_by_user or device_entry.name} - {entity_entry.name or entity_entry.original_name}" + else: + title = entity_entry.name or entity_entry.original_name + else: + title = entity_entry.name or entity_entry.original_name else: assert device_entry title = device_entry.name_by_user or device_entry.name return self.async_create_entry( - title=str(title), - data=self.data, + title=str(title), data=self.data, unique_id=unique_id ) return self.async_show_form( @@ -426,83 +823,159 @@ async def async_step_battery( CONF_BATTERY_LOW_TEMPLATE ): selector.TemplateSelector(), vol.Optional( - CONF_FILTER_OUTLIERS, - default=False): selector.BooleanSelector(), + CONF_FILTER_OUTLIERS, default=False + ): selector.BooleanSelector(), } ), errors=errors, ) + async def async_step_reconfigure( + self, user_input: dict[str, Any] | None = None + ) -> SubentryFlowResult: + """User flow to modify an existing battery note.""" + errors: dict[str, str] = {} -class OptionsFlowHandler(OptionsFlow): - """Handle an option flow for BatteryNotes.""" + config_subentry = self._get_reconfigure_subentry() - model_info: ModelInfo | None = None + if user_input is not None: + self.data[CONF_BATTERY_TYPE] = user_input[CONF_BATTERY_TYPE] + self.data[CONF_BATTERY_QUANTITY] = int(user_input[CONF_BATTERY_QUANTITY]) + self.data[CONF_BATTERY_LOW_THRESHOLD] = int( + user_input[CONF_BATTERY_LOW_THRESHOLD] + ) + if user_input.get(CONF_BATTERY_LOW_TEMPLATE, "") == "": + self.data[CONF_BATTERY_LOW_TEMPLATE] = None + else: + self.data[CONF_BATTERY_LOW_TEMPLATE] = user_input[ + CONF_BATTERY_LOW_TEMPLATE + ] + self.data[CONF_FILTER_OUTLIERS] = user_input.get( + CONF_FILTER_OUTLIERS, False + ) - def __init__(self) -> None: - """Initialize options flow.""" - self.current_config: dict - self.source_device_id: str - self.name: str - self.battery_type: str - self.battery_quantity: int - self.battery_low_template: str - self.filter_outliers: bool + # Save the updated subentry + new_title = user_input.pop(CONF_NAME) - async def async_step_init( - self, - user_input: dict[str, Any] | None = None, - ) -> ConfigFlowResult: - """Handle options flow.""" - errors = {} - self.current_config = dict(self.config_entry.data) - self.source_device_id = self.current_config.get(CONF_DEVICE_ID) # type: ignore - self.name = str(self.current_config.get(CONF_NAME) or "") - self.battery_type = str(self.current_config.get(CONF_BATTERY_TYPE) or "") - self.battery_quantity = int(self.current_config.get(CONF_BATTERY_QUANTITY) or 1) - self.battery_low_template = str( - self.current_config.get(CONF_BATTERY_LOW_TEMPLATE) or "" - ) - self.filter_outliers = bool( - self.current_config.get(CONF_FILTER_OUTLIERS) or False - ) + return self.async_update_and_abort( + self._get_entry(), + self._get_reconfigure_subentry(), + title=new_title, + data=self.data, + ) - if self.source_device_id: + self.data = config_subentry.data.copy() + + source_device_id = self.data.get(CONF_DEVICE_ID) + + if source_device_id: device_registry = dr.async_get(self.hass) - device_entry = device_registry.async_get(self.source_device_id) + device_entry = device_registry.async_get(source_device_id) if not device_entry: errors["base"] = "orphaned_battery_note" - else: - if device_entry and device_entry.manufacturer and device_entry.model: - _LOGGER.debug( - "Looking up device %s %s %s %s", - device_entry.manufacturer, - device_entry.model, - get_device_model_id(device_entry) or "", - device_entry.hw_version, - ) + elif device_entry and device_entry.manufacturer and device_entry.model: + _LOGGER.debug( + "Looking up device %s %s %s %s", + device_entry.manufacturer, + device_entry.model, + get_device_model_id(device_entry) or "", + device_entry.hw_version, + ) - self.model_info = ModelInfo( - device_entry.manufacturer, - device_entry.model, - get_device_model_id(device_entry), - device_entry.hw_version, - ) + self.model_info = ModelInfo( + device_entry.manufacturer, + device_entry.model, + get_device_model_id(device_entry), + device_entry.hw_version, + ) - schema = self.build_options_schema() - if user_input is not None: - user_input[CONF_BATTERY_QUANTITY] = int(user_input[CONF_BATTERY_QUANTITY]) - user_input[CONF_BATTERY_LOW_THRESHOLD] = int( - user_input[CONF_BATTERY_LOW_THRESHOLD] + if self.data.get(CONF_BATTERY_LOW_TEMPLATE, None) is None: + data_schema = vol.Schema( + { + vol.Optional( + CONF_NAME, default=config_subentry.title + ): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.TEXT + ), + ), + vol.Required( + CONF_BATTERY_TYPE, default=self.data[CONF_BATTERY_TYPE] + ): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.TEXT + ), + ), + vol.Required( + CONF_BATTERY_QUANTITY, default=self.data[CONF_BATTERY_QUANTITY] + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, max=100, mode=selector.NumberSelectorMode.BOX + ), + ), + vol.Required( + CONF_BATTERY_LOW_THRESHOLD, + default=self.data.get(CONF_BATTERY_LOW_THRESHOLD, 0), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=99, mode=selector.NumberSelectorMode.BOX + ), + ), + vol.Optional( + CONF_BATTERY_LOW_TEMPLATE + ): selector.TemplateSelector(), + vol.Optional( + CONF_FILTER_OUTLIERS, + default=self.data.get(CONF_FILTER_OUTLIERS, False), + ): selector.BooleanSelector(), + } + ) + else: + data_schema = vol.Schema( + { + vol.Optional( + CONF_NAME, default=config_subentry.title + ): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.TEXT + ), + ), + vol.Required( + CONF_BATTERY_TYPE, default=self.data[CONF_BATTERY_TYPE] + ): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.TEXT + ), + ), + vol.Required( + CONF_BATTERY_QUANTITY, default=self.data[CONF_BATTERY_QUANTITY] + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, max=100, mode=selector.NumberSelectorMode.BOX + ), + ), + vol.Required( + CONF_BATTERY_LOW_THRESHOLD, + default=self.data.get(CONF_BATTERY_LOW_THRESHOLD, 0), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=99, mode=selector.NumberSelectorMode.BOX + ), + ), + vol.Optional( + CONF_BATTERY_LOW_TEMPLATE, + default=self.data.get(CONF_BATTERY_LOW_TEMPLATE, None), + ): selector.TemplateSelector(), + vol.Optional( + CONF_FILTER_OUTLIERS, + default=self.data.get(CONF_FILTER_OUTLIERS, False), + ): selector.BooleanSelector(), + } ) - # user_input[CONF_BATTERY_LOW_TEMPLATE] = user_input.get(CONF_BATTERY_LOW_TEMPLATE, None) - errors = await self.save_options(user_input, schema) - if not errors: - return self.async_create_entry(title="", data={}) return self.async_show_form( - step_id="init", + step_id="reconfigure", description_placeholders={ "manufacturer": self.model_info.manufacturer if self.model_info else "", "model": self.model_info.model if self.model_info else "", @@ -513,115 +986,6 @@ async def async_step_init( str(self.model_info.hw_version or "") if self.model_info else "" ), }, - data_schema=schema, + data_schema=data_schema, errors=errors, ) - - async def save_options( - self, - user_input: dict[str, Any], - schema: vol.Schema, - ) -> dict: - """Save options, and return errors when validation fails.""" - errors = {} - - device_registry = dr.async_get(self.hass) - device_entry = device_registry.async_get( - str(self.config_entry.data.get(CONF_DEVICE_ID)) - ) - - source_entity_id = self.config_entry.data.get(CONF_SOURCE_ENTITY_ID, None) - - entity_entry: er.RegistryEntry | None = None - if source_entity_id: - entity_registry = er.async_get(self.hass) - entity_entry = entity_registry.async_get(source_entity_id) - if not entity_entry: - errors["base"] = "orphaned_battery_note" - return errors - else: - if not device_entry: - errors["base"] = "orphaned_battery_note" - return errors - - title: Any = "" - if CONF_NAME in user_input: - title = user_input.get(CONF_NAME) - elif source_entity_id and entity_entry: - title = entity_entry.name or entity_entry.original_name - elif device_entry: - title = device_entry.name_by_user or device_entry.name - - self._process_user_input(user_input, schema) - self.hass.config_entries.async_update_entry( - self.config_entry, - title=title, - data=self.current_config, - ) - return {} - - def _process_user_input( - self, - user_input: dict[str, Any], - schema: vol.Schema, - ) -> None: - """Process the provided user input against the schema.""" - for key in schema.schema: - if isinstance(key, vol.Marker): - key = key.schema - if key in user_input: - self.current_config[key] = user_input.get(key) - elif key in self.current_config: - self.current_config.pop(key) - - def build_options_schema(self) -> vol.Schema: - """Build the options schema.""" - data_schema = vol.Schema( - { - vol.Optional(CONF_NAME): selector.TextSelector( - selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT), - ), - vol.Required(CONF_BATTERY_TYPE): selector.TextSelector( - selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT), - ), - vol.Required(CONF_BATTERY_QUANTITY): selector.NumberSelector( - selector.NumberSelectorConfig( - min=1, max=100, mode=selector.NumberSelectorMode.BOX - ), - ), - vol.Required(CONF_BATTERY_LOW_THRESHOLD): selector.NumberSelector( - selector.NumberSelectorConfig( - min=0, max=99, mode=selector.NumberSelectorMode.BOX - ), - ), - vol.Optional(CONF_BATTERY_LOW_TEMPLATE): selector.TemplateSelector(), - vol.Optional(CONF_FILTER_OUTLIERS): selector.BooleanSelector(), - } - ) - - return _fill_schema_defaults( - data_schema, - self.current_config, - ) - - -def _fill_schema_defaults( - data_schema: vol.Schema, - options: dict[str, str], -) -> vol.Schema: - """Make a copy of the schema with suggested values set to saved options.""" - schema = {} - for key, val in data_schema.schema.items(): - new_key = key - if key in options and isinstance(key, vol.Marker): - if ( - isinstance(key, vol.Optional) - and callable(key.default) - and key.default() - ): - new_key = vol.Optional(key.schema, default=options.get(key)) # type: ignore - else: - new_key = copy.copy(key) - new_key.description = {"suggested_value": options.get(key)} # type: ignore - schema[new_key] = val - return vol.Schema(schema) diff --git a/custom_components/battery_notes/const.py b/custom_components/battery_notes/const.py index 6a84082ed..b066fdaad 100644 --- a/custom_components/battery_notes/const.py +++ b/custom_components/battery_notes/const.py @@ -1,17 +1,18 @@ """Constants for battery_notes.""" import json +from typing import Final from logging import Logger, getLogger from pathlib import Path -from typing import Final import voluptuous as vol + from homeassistant.const import Platform from homeassistant.helpers import config_validation as cv LOGGER: Logger = getLogger(__package__) -MIN_HA_VERSION = "2025.4.0" +MIN_HA_VERSION = "2025.9.0" manifestfile = Path(__file__).parent / "manifest.json" with open(file=manifestfile, encoding="UTF-8") as json_file: @@ -26,8 +27,6 @@ LAST_REPORTED = "battery_last_reported" LAST_REPORTED_LEVEL = "battery_last_reported_level" -DOMAIN_CONFIG = "config" - DEFAULT_BATTERY_LOW_THRESHOLD = 10 DEFAULT_BATTERY_INCREASE_THRESHOLD = 25 DEFAULT_LIBRARY_URL = "https://battery-notes-data.codechimp.org/library.json" @@ -42,10 +41,9 @@ CONF_USER_LIBRARY = "user_library" CONF_MODEL = "model" CONF_MODEL_ID = "model_id" +CONF_HW_VERSION = "hw_version" CONF_MANUFACTURER = "manufacturer" CONF_DEVICE_NAME = "device_name" -CONF_LIBRARY_URL = "library_url" -CONF_SCHEMA_URL = "schema_url" CONF_SHOW_ALL_DEVICES = "show_all_devices" CONF_ENABLE_REPLACED = "enable_replaced" CONF_DEFAULT_BATTERY_LOW_THRESHOLD = "default_battery_low_threshold" @@ -54,6 +52,7 @@ CONF_ROUND_BATTERY = "round_battery" CONF_BATTERY_LOW_TEMPLATE = "battery_low_template" CONF_FILTER_OUTLIERS = "filter_outliers" +CONF_ADVANCED_SETTINGS = "advanced_settings" DATA_CONFIGURED_ENTITIES = "configured_entities" DATA_DISCOVERED_ENTITIES = "discovered_entities" @@ -93,6 +92,8 @@ WINDOW_SIZE_UNIT_NUMBER_EVENTS = 1 WINDOW_SIZE_UNIT_TIME = 2 +ISSUE_DEPRECATED_YAML = "deprecated_yaml" + SERVICE_BATTERY_REPLACED_SCHEMA = vol.Schema( { vol.Optional(ATTR_DEVICE_ID): cv.string, @@ -108,7 +109,9 @@ ) PLATFORMS: Final = [ + Platform.BINARY_SENSOR, Platform.BUTTON, Platform.SENSOR, - Platform.BINARY_SENSOR, ] + +SUBENTRY_BATTERY_NOTE = "battery_note" diff --git a/custom_components/battery_notes/coordinator.py b/custom_components/battery_notes/coordinator.py index 2225fff2c..6315d45ef 100644 --- a/custom_components/battery_notes/coordinator.py +++ b/custom_components/battery_notes/coordinator.py @@ -3,69 +3,72 @@ from __future__ import annotations import logging -from dataclasses import dataclass -from datetime import datetime from typing import cast +from datetime import datetime +from dataclasses import dataclass -from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN -from homeassistant.components.binary_sensor import BinarySensorDeviceClass -from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN -from homeassistant.components.sensor import SensorDeviceClass -from homeassistant.config_entries import ConfigEntry +from homeassistant.core import CALLBACK_TYPE, HomeAssistant from homeassistant.const import ( - CONF_DEVICE_ID, PERCENTAGE, - STATE_UNAVAILABLE, STATE_UNKNOWN, + CONF_DEVICE_ID, + STATE_UNAVAILABLE, ) -from homeassistant.core import CALLBACK_TYPE, HomeAssistant -from homeassistant.helpers import device_registry as dr -from homeassistant.helpers import entity_registry as er -from homeassistant.helpers import issue_registry as ir +from homeassistant.helpers import ( + issue_registry as ir, + device_registry as dr, + entity_registry as er, +) +from homeassistant.config_entries import ConfigEntry, ConfigSubentry +from homeassistant.util.hass_dict import HassKey +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, SensorDeviceClass from homeassistant.helpers.entity_registry import RegistryEntry +from homeassistant.components.binary_sensor import ( + DOMAIN as BINARY_SENSOR_DOMAIN, + BinarySensorDeviceClass, +) from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from homeassistant.util.hass_dict import HassKey -from .common import utcnow_no_timezone, validate_is_float from .const import ( - ATTR_BATTERY_LAST_REPLACED, - ATTR_BATTERY_LEVEL, - ATTR_BATTERY_LOW, - ATTR_BATTERY_LOW_THRESHOLD, - ATTR_BATTERY_QUANTITY, - ATTR_BATTERY_THRESHOLD_REMINDER, - ATTR_BATTERY_TYPE, - ATTR_BATTERY_TYPE_AND_QUANTITY, + DOMAIN, + ATTR_REMOVE, + LAST_REPLACED, + LAST_REPORTED, ATTR_DEVICE_ID, + ATTR_BATTERY_LOW, ATTR_DEVICE_NAME, - ATTR_PREVIOUS_BATTERY_LEVEL, - ATTR_REMOVE, - ATTR_SOURCE_ENTITY_ID, - CONF_BATTERY_LOW_TEMPLATE, - CONF_BATTERY_LOW_THRESHOLD, - CONF_BATTERY_QUANTITY, + ATTR_BATTERY_TYPE, CONF_BATTERY_TYPE, + ATTR_BATTERY_LEVEL, + LAST_REPORTED_LEVEL, CONF_FILTER_OUTLIERS, + ATTR_BATTERY_QUANTITY, + ATTR_SOURCE_ENTITY_ID, + CONF_BATTERY_QUANTITY, CONF_SOURCE_ENTITY_ID, - DEFAULT_BATTERY_INCREASE_THRESHOLD, - DEFAULT_BATTERY_LOW_THRESHOLD, - DEFAULT_LIBRARY_URL, - DEFAULT_SCHEMA_URL, - DOMAIN, EVENT_BATTERY_INCREASED, EVENT_BATTERY_THRESHOLD, - LAST_REPLACED, - LAST_REPORTED, - LAST_REPORTED_LEVEL, + CONF_BATTERY_LOW_TEMPLATE, + ATTR_BATTERY_LAST_REPLACED, + ATTR_BATTERY_LOW_THRESHOLD, + CONF_BATTERY_LOW_THRESHOLD, + ATTR_PREVIOUS_BATTERY_LEVEL, + DEFAULT_BATTERY_LOW_THRESHOLD, + ATTR_BATTERY_TYPE_AND_QUANTITY, + ATTR_BATTERY_THRESHOLD_REMINDER, + DEFAULT_BATTERY_INCREASE_THRESHOLD, ) -from .filters import LowOutlierFilter from .store import BatteryNotesStorage +from .common import validate_is_float, utcnow_no_timezone +from .filters import LowOutlierFilter _LOGGER = logging.getLogger(__name__) + @dataclass class BatteryNotesDomainConfig: """Class for sharing config data within the BatteryNotes integration.""" + enable_autodiscovery: bool = True show_all_devices: bool = False enable_replaced: bool = True @@ -73,26 +76,27 @@ class BatteryNotesDomainConfig: round_battery: bool = False default_battery_low_threshold: int = DEFAULT_BATTERY_LOW_THRESHOLD battery_increased_threshod: int = DEFAULT_BATTERY_INCREASE_THRESHOLD - library_url: str = DEFAULT_LIBRARY_URL - schema_url: str = DEFAULT_SCHEMA_URL library_last_update: datetime | None = None user_library: str = "" store: BatteryNotesStorage | None = None + MY_KEY: HassKey[BatteryNotesDomainConfig] = HassKey(DOMAIN) type BatteryNotesConfigEntry = ConfigEntry[BatteryNotesData] + @dataclass class BatteryNotesData: """Class for sharing data within the BatteryNotes integration.""" domain_config: BatteryNotesDomainConfig store: BatteryNotesStorage - coordinator: BatteryNotesCoordinator | None = None + loaded_subentries: dict[str, ConfigSubentry] + subentry_coordinators: dict[str, BatteryNotesSubentryCoordinator] | None = None -class BatteryNotesCoordinator(DataUpdateCoordinator[None]): +class BatteryNotesSubentryCoordinator(DataUpdateCoordinator[None]): """Define an object to hold Battery Notes device.""" config_entry: BatteryNotesConfigEntry @@ -105,6 +109,7 @@ class BatteryNotesCoordinator(DataUpdateCoordinator[None]): battery_low_template: str | None wrapped_battery: RegistryEntry | None = None wrapped_battery_low: RegistryEntry | None = None + is_orphaned: bool = False _current_battery_level: str | None = None _previous_battery_low: bool | None = None _previous_battery_level: str | None = None @@ -115,10 +120,11 @@ class BatteryNotesCoordinator(DataUpdateCoordinator[None]): _source_entity_name: str | None = None _outlier_filter: LowOutlierFilter | None = None - def __init__( + def __init__( # noqa: PLR0912 self, hass: HomeAssistant, config_entry: BatteryNotesConfigEntry, + subentry: ConfigSubentry, ): """Initialize.""" super().__init__(hass, _LOGGER, config_entry=config_entry, name=DOMAIN) @@ -126,35 +132,36 @@ def __init__( self.reset_jobs: list[CALLBACK_TYPE] = [] self.config_entry = config_entry + self.subentry = subentry - self.device_id = config_entry.data.get(CONF_DEVICE_ID, None) - self.source_entity_id = config_entry.data.get(CONF_SOURCE_ENTITY_ID, None) + self.device_id = self.subentry.data.get(CONF_DEVICE_ID, None) + self.source_entity_id = self.subentry.data.get(CONF_SOURCE_ENTITY_ID, None) if not self._link_to_source(): self.is_orphaned = True return - self.battery_type = cast(str, self.config_entry.data.get(CONF_BATTERY_TYPE)) + self.battery_type = cast(str, self.subentry.data.get(CONF_BATTERY_TYPE, "")) try: self.battery_quantity = cast( - int, self.config_entry.data.get(CONF_BATTERY_QUANTITY) + int, self.subentry.data.get(CONF_BATTERY_QUANTITY, 1) ) except ValueError: self.battery_quantity = 1 self.battery_low_threshold = int( - self.config_entry.data.get(CONF_BATTERY_LOW_THRESHOLD, 0) + self.subentry.data.get(CONF_BATTERY_LOW_THRESHOLD, 0) ) if hasattr(self.config_entry, "runtime_data"): if self.battery_low_threshold == 0: self.battery_low_threshold = self.config_entry.runtime_data.domain_config.default_battery_low_threshold - self.battery_low_template = self.config_entry.data.get( - CONF_BATTERY_LOW_TEMPLATE + self.battery_low_template = self.subentry.data.get( + CONF_BATTERY_LOW_TEMPLATE, None ) - if config_entry.data.get(CONF_FILTER_OUTLIERS, False): + if self.subentry.data.get(CONF_FILTER_OUTLIERS, False): self._outlier_filter = LowOutlierFilter(window_size=3, radius=80) _LOGGER.debug("Outlier filter enabled") @@ -201,7 +208,7 @@ def __init__( ) self.last_reported = last_reported - def _link_to_source(self) -> bool: + def _link_to_source(self) -> bool: # noqa: PLR0912 """Get the source device or entity, determine name and associate our wrapped battery if available.""" device_registry = dr.async_get(self.hass) entity_registry = er.async_get(self.hass) @@ -213,9 +220,10 @@ def _link_to_source(self) -> bool: ir.async_create_issue( self.hass, DOMAIN, - f"missing_device_{self.config_entry.entry_id}", + f"missing_device_{self.subentry.subentry_id}", data={ "entry_id": self.config_entry.entry_id, + "subentry_id": self.subentry.subentry_id, "device_id": self.device_id, "source_entity_id": self.source_entity_id, }, @@ -223,13 +231,13 @@ def _link_to_source(self) -> bool: severity=ir.IssueSeverity.WARNING, translation_key="missing_device", translation_placeholders={ - "name": self.config_entry.title, + "name": self.subentry.title, }, ) _LOGGER.warning( "%s is orphaned, unable to find entity %s", - self.config_entry.entry_id, + self.subentry.subentry_id, self.source_entity_id, ) return False @@ -248,27 +256,15 @@ def _link_to_source(self) -> bool: entity.unit_of_measurement, ) - if entity.device_id: - device_entry = device_registry.async_get(entity.device_id) - if device_entry: - self.device_name = ( - device_entry.name_by_user - or device_entry.name - or self.config_entry.title - ) - else: - self.device_name = self.config_entry.title - else: - self.device_name = self.config_entry.title + self.device_name = self.subentry.title else: for entity in entity_registry.entities.values(): - if not entity.device_id or entity.device_id != self.device_id: continue - if ( - not entity.domain - or entity.domain not in [SENSOR_DOMAIN, BINARY_SENSOR_DOMAIN] - ): + if not entity.domain or entity.domain not in [ + SENSOR_DOMAIN, + BINARY_SENSOR_DOMAIN, + ]: continue if not entity.platform or entity.platform == DOMAIN: continue @@ -300,17 +296,19 @@ def _link_to_source(self) -> bool: device_entry = device_registry.async_get(self.device_id) if device_entry: self.device_name = ( - device_entry.name_by_user or device_entry.name or self.config_entry.title + self.subentry.title + or device_entry.name_by_user + or device_entry.name ) else: - self.device_name = self.config_entry.title + self.device_name = self.subentry.title ir.async_create_issue( self.hass, DOMAIN, - f"missing_device_{self.config_entry.entry_id}", + f"missing_device_{self.subentry.subentry_id}", data={ - "entry_id": self.config_entry.entry_id, + "entry_id": self.subentry.subentry_id, "device_id": self.device_id, "source_entity_id": self.source_entity_id, }, @@ -318,13 +316,13 @@ def _link_to_source(self) -> bool: severity=ir.IssueSeverity.WARNING, translation_key="missing_device", translation_placeholders={ - "name": self.config_entry.title, + "name": self.subentry.title, }, ) _LOGGER.warning( "%s is orphaned, unable to find device %s", - self.config_entry.entry_id, + self.subentry.subentry_id, self.device_id, ) return False @@ -332,12 +330,9 @@ def _link_to_source(self) -> bool: return True @property - def fake_device(self) -> bool: - """Return if an actual device registry entry.""" - if self.config_entry.data.get(CONF_SOURCE_ENTITY_ID, None): - if self.config_entry.data.get(CONF_DEVICE_ID, None) is None: - return True - return False + def unique_id(self) -> str: + """Return a unique ID for the coordinator.""" + return f"{self.config_entry.entry_id}_{self.subentry.subentry_id}" @property def source_entity_name(self): @@ -349,19 +344,32 @@ def source_entity_name(self): entity_registry = er.async_get(self.hass) device_registry = dr.async_get(self.hass) registry_entry = entity_registry.async_get(self.source_entity_id) - device_entry = device_registry.async_get(self.device_id) if self.device_id else None - assert(registry_entry) - - if registry_entry.name is None and registry_entry.has_entity_name and device_entry: - self._source_entity_name = ( - registry_entry.name or registry_entry.original_name or device_entry.name_by_user or device_entry.name or self.source_entity_id - ) - else: - self._source_entity_name = ( - registry_entry.name or registry_entry.original_name or self.source_entity_id - ) + device_entry = ( + device_registry.async_get(self.device_id) + if self.device_id + else None + ) - assert(self._source_entity_name) + if registry_entry: + if ( + registry_entry + and registry_entry.name is None + and registry_entry.has_entity_name + and device_entry + ): + self._source_entity_name = ( + registry_entry.name + or registry_entry.original_name + or device_entry.name_by_user + or device_entry.name + or self.source_entity_id + ) + else: + self._source_entity_name = ( + registry_entry.name + or registry_entry.original_name + or self.source_entity_id + ) return self._source_entity_name @@ -377,7 +385,8 @@ def battery_low_template_state(self, value): if ( self._previous_battery_low_template_state is not None and self.battery_low_template - and value not in [ + and value + not in [ STATE_UNAVAILABLE, STATE_UNKNOWN, ] @@ -407,7 +416,8 @@ def battery_low_template_state(self, value): if ( self._previous_battery_low_template_state and not self._battery_low_template_state - and value not in [ + and value + not in [ STATE_UNAVAILABLE, STATE_UNKNOWN, ] @@ -431,7 +441,6 @@ def battery_low_template_state(self, value): _LOGGER.debug("battery_increased event fired via template") - self._previous_battery_low_template_state = value @property @@ -443,11 +452,10 @@ def battery_low_binary_state(self): def battery_low_binary_state(self, value): """Set the current battery low status from a binary sensor and fire events if valid.""" self._battery_low_binary_state = value - if (self._previous_battery_low_binary_state is not None - and value not in [ - STATE_UNAVAILABLE, - STATE_UNKNOWN, - ]): + if self._previous_battery_low_binary_state is not None and value not in [ + STATE_UNAVAILABLE, + STATE_UNKNOWN, + ]: self.hass.bus.async_fire( EVENT_BATTERY_THRESHOLD, { @@ -474,7 +482,8 @@ def battery_low_binary_state(self, value): if ( self._previous_battery_low_binary_state and not self._battery_low_binary_state - and value not in [ + and value + not in [ STATE_UNAVAILABLE, STATE_UNKNOWN, ] @@ -517,7 +526,9 @@ def current_battery_level(self, value): "Checking outlier (%s=%s) -> %s", self.device_id or self.source_entity_id or "", value, - "skip" if self._outlier_filter.skip_processing else self._outlier_filter.filter_state(value), + "skip" + if self._outlier_filter.skip_processing + else self._outlier_filter.filter_state(value), ) if self._outlier_filter.skip_processing: return @@ -550,7 +561,9 @@ def current_battery_level(self, value): _LOGGER.debug("battery_threshold event fired Low: %s", self.battery_low) # Battery increased event - increase_threshold = self.config_entry.runtime_data.domain_config.battery_increased_threshod + increase_threshold = ( + self.config_entry.runtime_data.domain_config.battery_increased_threshod + ) if self._current_battery_level not in [STATE_UNAVAILABLE, STATE_UNKNOWN]: if ( @@ -598,9 +611,13 @@ def last_replaced(self) -> datetime | None: return None if self.source_entity_id: - entry = self.config_entry.runtime_data.store.async_get_entity(self.source_entity_id) + entry = self.config_entry.runtime_data.store.async_get_entity( + self.source_entity_id + ) else: - entry = self.config_entry.runtime_data.store.async_get_device(self.device_id) + entry = self.config_entry.runtime_data.store.async_get_device( + self.device_id + ) if entry: if LAST_REPLACED in entry and entry[LAST_REPLACED] is not None: @@ -631,9 +648,13 @@ def last_reported(self) -> datetime | None: return None if self.source_entity_id: - entry = self.config_entry.runtime_data.store.async_get_entity(self.source_entity_id) + entry = self.config_entry.runtime_data.store.async_get_entity( + self.source_entity_id + ) else: - entry = self.config_entry.runtime_data.store.async_get_device(self.device_id) + entry = self.config_entry.runtime_data.store.async_get_device( + self.device_id + ) if entry: if LAST_REPORTED in entry: @@ -657,7 +678,7 @@ def last_reported(self, value): if self.source_entity_id: self.async_update_entity_config(entity_id=self.source_entity_id, data=entry) else: - assert(self.device_id) + assert self.device_id self.async_update_device_config(device_id=self.device_id, data=entry) @property @@ -667,9 +688,13 @@ def last_reported_level(self) -> float | None: return None if self.source_entity_id: - entry = self.config_entry.runtime_data.store.async_get_entity(self.source_entity_id) + entry = self.config_entry.runtime_data.store.async_get_entity( + self.source_entity_id + ) else: - entry = self.config_entry.runtime_data.store.async_get_device(self.device_id) + entry = self.config_entry.runtime_data.store.async_get_device( + self.device_id + ) if entry: if LAST_REPORTED_LEVEL in entry: @@ -686,7 +711,7 @@ def last_reported_level(self, value: float): if self.source_entity_id: self.async_update_entity_config(entity_id=self.source_entity_id, data=entry) else: - assert(self.device_id) + assert self.device_id self.async_update_device_config(device_id=self.device_id, data=entry) @property @@ -717,7 +742,12 @@ def rounded_previous_battery_level(self) -> float: def _rounded_level(self, value) -> float: """Round the level, if preferred.""" if validate_is_float(value): - return round(float(value), None if self.config_entry.runtime_data.domain_config.round_battery else 1) + return round( + float(value), + None + if self.config_entry.runtime_data.domain_config.round_battery + else 1, + ) else: return value diff --git a/custom_components/battery_notes/diagnostics.py b/custom_components/battery_notes/diagnostics.py index 280bb097f..ab3e46b50 100644 --- a/custom_components/battery_notes/diagnostics.py +++ b/custom_components/battery_notes/diagnostics.py @@ -4,17 +4,15 @@ from typing import Any -from homeassistant.const import CONF_DEVICE_ID from homeassistant.core import HomeAssistant +from homeassistant.const import CONF_DEVICE_ID from homeassistant.helpers import ( device_registry as dr, -) -from homeassistant.helpers import ( entity_registry as er, ) -from .common import get_device_model_id from .const import CONF_SOURCE_ENTITY_ID +from .common import get_device_model_id from .coordinator import BatteryNotesConfigEntry @@ -26,25 +24,26 @@ async def async_get_config_entry_diagnostics( device_registry = dr.async_get(hass) entity_registry = er.async_get(hass) - device_id = config_entry.data.get(CONF_DEVICE_ID, None) - source_entity_id = config_entry.data.get(CONF_SOURCE_ENTITY_ID, None) - - if source_entity_id: - entity = entity_registry.async_get(source_entity_id) - if entity: - device_id = entity.device_id - diagnostics = {"entry": config_entry.as_dict()} - if device_id: - device_entry = device_registry.async_get(device_id) - if device_entry: - device_info = { - "manufacturer": device_entry.manufacturer, - "model": device_entry.model, - "model_id": get_device_model_id(device_entry), - "hw_version": device_entry.hw_version, - } - diagnostics.update({"device": device_info}) + for subentry in config_entry.subentries.values(): + device_id = subentry.data.get(CONF_DEVICE_ID, None) + source_entity_id = subentry.data.get(CONF_SOURCE_ENTITY_ID, None) + + if source_entity_id: + entity = entity_registry.async_get(source_entity_id) + if entity: + device_id = entity.device_id + + if device_id: + device_entry = device_registry.async_get(device_id) + if device_entry: + device_info = { + "manufacturer": device_entry.manufacturer, + "model": device_entry.model, + "model_id": get_device_model_id(device_entry), + "hw_version": device_entry.hw_version, + } + diagnostics.update({f"subentry {subentry.subentry_id}": device_info}) return diagnostics diff --git a/custom_components/battery_notes/discovery.py b/custom_components/battery_notes/discovery.py index a9a8c0276..38e6b746c 100644 --- a/custom_components/battery_notes/discovery.py +++ b/custom_components/battery_notes/discovery.py @@ -6,23 +6,24 @@ from typing import Any import homeassistant.helpers.device_registry as dr -from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY -from homeassistant.const import CONF_DEVICE_ID from homeassistant.core import HomeAssistant, callback +from homeassistant.const import CONF_DEVICE_ID from homeassistant.helpers import discovery_flow +from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_INTEGRATION_DISCOVERY -from .common import get_device_model_id from .const import ( - CONF_BATTERY_QUANTITY, - CONF_BATTERY_TYPE, - CONF_DEVICE_NAME, - CONF_MANUFACTURER, + DOMAIN, CONF_MODEL, CONF_MODEL_ID, - DOMAIN, + CONF_HW_VERSION, + CONF_DEVICE_NAME, + CONF_BATTERY_TYPE, + CONF_MANUFACTURER, + CONF_BATTERY_QUANTITY, ) +from .common import get_device_model_id +from .library import DATA_LIBRARY, ModelInfo, DeviceBatteryDetails from .coordinator import BatteryNotesDomainConfig -from .library import DeviceBatteryDetails, Library, ModelInfo _LOGGER = logging.getLogger(__name__) @@ -77,7 +78,9 @@ class DiscoveryManager: so the user can add them to their HA instance. """ - def __init__(self, hass: HomeAssistant, ha_config: BatteryNotesDomainConfig) -> None: + def __init__( + self, hass: HomeAssistant, ha_config: BatteryNotesDomainConfig + ) -> None: """Init.""" self.hass = hass self.ha_config = ha_config @@ -87,10 +90,11 @@ async def start_discovery(self) -> None: _LOGGER.debug("Start auto discovering devices") device_registry = dr.async_get(self.hass) - library = Library(self.hass) - await library.load_libraries() + library = self.hass.data[DATA_LIBRARY] + if not library.is_loaded: + await library.load_libraries() - if library.loaded(): + if library.is_loaded: for device_entry in list(device_registry.devices.values()): if not self.should_process_device(device_entry): continue @@ -121,10 +125,7 @@ async def start_discovery(self) -> None: def should_process_device(self, device_entry: dr.DeviceEntry) -> bool: """Do some validations on the registry entry to see if it qualifies for discovery.""" - if device_entry.disabled: - return False - - return True + return not device_entry.disabled @callback def _init_entity_discovery( @@ -133,17 +134,32 @@ def _init_entity_discovery( device_battery_details: DeviceBatteryDetails, ) -> None: """Dispatch the discovery flow for a given entity.""" - existing_entries = [ - entry - for entry in self.hass.config_entries.async_entries(DOMAIN) - if entry.unique_id == f"bn_{device_entry.id}" - ] - if existing_entries: - _LOGGER.debug( - "%s: Already setup, skipping new discovery", - f"bn_{device_entry.id}", - ) - return + unique_id = f"bn_{device_entry.id}" + + # Iterate all the ignored devices and check if we have it already + for config_entry in self.hass.config_entries.async_entries( + domain=DOMAIN, include_ignore=True, include_disabled=False + ): + if ( + config_entry.source == SOURCE_IGNORE + and config_entry.unique_id == unique_id + ): + _LOGGER.debug( + "%s: Ignored, skipping new discovery", + unique_id, + ) + return + + for config_entry in self.hass.config_entries.async_entries( + domain=DOMAIN, include_ignore=False, include_disabled=False + ): + for subentry in config_entry.subentries.values(): + if subentry.data.get(CONF_DEVICE_ID, "") == device_entry.id: + _LOGGER.debug( + "%s: Already setup, skipping new discovery", + unique_id, + ) + return discovery_data: dict[str, Any] = { CONF_DEVICE_ID: device_entry.id, @@ -156,7 +172,8 @@ def _init_entity_discovery( ) discovery_data[CONF_MANUFACTURER] = device_battery_details.manufacturer discovery_data[CONF_MODEL] = device_battery_details.model - discovery_data[CONF_MODEL_ID] = get_device_model_id(device_entry), + discovery_data[CONF_MODEL_ID] = get_device_model_id(device_entry) + discovery_data[CONF_HW_VERSION] = device_battery_details.hw_version discovery_data[CONF_DEVICE_NAME] = get_wrapped_device_name( device_entry.id, device_entry ) diff --git a/custom_components/battery_notes/entity.py b/custom_components/battery_notes/entity.py index b6f621a74..519010944 100644 --- a/custom_components/battery_notes/entity.py +++ b/custom_components/battery_notes/entity.py @@ -4,7 +4,13 @@ from dataclasses import dataclass +from homeassistant.core import HomeAssistant, split_entity_id +from homeassistant.helpers import device_registry as dr +from homeassistant.helpers.device import async_entity_id_to_device_id from homeassistant.helpers.entity import EntityDescription +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .coordinator import BatteryNotesSubentryCoordinator @dataclass(frozen=True, kw_only=True) @@ -12,8 +18,79 @@ class BatteryNotesRequiredKeysMixin: """Mixin for required keys.""" unique_id_suffix: str + entity_type: str + require_device: bool = False @dataclass(frozen=True, kw_only=True) class BatteryNotesEntityDescription(EntityDescription, BatteryNotesRequiredKeysMixin): """Generic Battery Notes entity description.""" + + +class BatteryNotesEntity(CoordinatorEntity[BatteryNotesSubentryCoordinator]): + """Base class for Battery Notes entities.""" + + coordinator: BatteryNotesSubentryCoordinator + entity_description: BatteryNotesEntityDescription + + def __init__( + self, + hass: HomeAssistant, + entity_description: BatteryNotesEntityDescription, + coordinator: BatteryNotesSubentryCoordinator, + ) -> None: + """Initialize the base entity.""" + super().__init__(coordinator) + + device_registry = dr.async_get(hass) + + self.entity_description = entity_description + self.coordinator = coordinator + + self._attr_has_entity_name = True + + # Set up entity naming and translation placeholders + self._set_entity_id(entity_description) + + # Set up device association + self._associate_device(hass, device_registry) + + def _set_entity_id(self, entity_description: BatteryNotesEntityDescription) -> None: + """Set up entity naming and translation placeholders.""" + if self.coordinator.source_entity_id and not self.coordinator.device_id: + self._attr_translation_placeholders = { + "device_name": self.coordinator.device_name + " " + } + self.entity_id = f"{entity_description.entity_type}.{self.coordinator.device_name.lower()}_{entity_description.key}" + elif self.coordinator.source_entity_id and self.coordinator.device_id: + _, source_object_id = split_entity_id(self.coordinator.source_entity_id) + self._attr_translation_placeholders = { + "device_name": self.coordinator.source_entity_name + " " + } + self.entity_id = f"{entity_description.entity_type}.{source_object_id}_{entity_description.key}" + else: + self._attr_translation_placeholders = {"device_name": ""} + self.entity_id = f"{entity_description.entity_type}.{self.coordinator.device_name.lower()}_{entity_description.key}" + + def _associate_device( + self, hass: HomeAssistant, device_registry: dr.DeviceRegistry + ) -> None: + """Set up device association.""" + if self.coordinator.device_id and ( + device_registry.async_get(self.coordinator.device_id) + ): + # Attach to the device_id + self.device_entry = device_registry.async_get(self.coordinator.device_id) + elif ( + self.entity_description.require_device is False + and self.coordinator.source_entity_id + ): + device_id = async_entity_id_to_device_id( + hass, self.coordinator.source_entity_id + ) + # source_entity_id is attached to a device, use that and add + if device_id and (device_entry := device_registry.async_get(device_id)): + self.device_entry = device_entry + else: + # No device, leave hanging + self.device_entry = None diff --git a/custom_components/battery_notes/filters.py b/custom_components/battery_notes/filters.py index 77f56fe38..dfb7910a2 100644 --- a/custom_components/battery_notes/filters.py +++ b/custom_components/battery_notes/filters.py @@ -2,13 +2,13 @@ import logging import statistics -from collections import Counter, deque -from datetime import timedelta -from numbers import Number from typing import cast +from numbers import Number +from datetime import timedelta +from collections import Counter, deque +from .const import WINDOW_SIZE_UNIT_TIME, WINDOW_SIZE_UNIT_NUMBER_EVENTS from .common import utcnow_no_timezone -from .const import WINDOW_SIZE_UNIT_NUMBER_EVENTS, WINDOW_SIZE_UNIT_TIME _LOGGER = logging.getLogger(__name__) @@ -35,7 +35,7 @@ def __repr__(self) -> str: return f"{self.timestamp} : {self.state}" -class Filter(): +class Filter: """Base filter class.""" def __init__( @@ -91,6 +91,7 @@ def filter_state(self, new_state: int | float | str) -> int | float | str: new_state = filtered.state return new_state + class LowOutlierFilter(Filter): """Low Outlier filter. @@ -106,9 +107,7 @@ def __init__( :param radius: band radius """ - super().__init__( - window_size - ) + super().__init__(window_size) self._radius = radius self._stats_internal: Counter = Counter() self._store_raw = True @@ -122,10 +121,10 @@ def _filter_state(self, new_state: FilterState) -> FilterState: if previous_state_values and new_state_value >= previous_state_values[-1]: _LOGGER.debug( - "New value higher than last previous state, allowing. %s >= %s", - new_state, - previous_state_values[-1] - ) + "New value higher than last previous state, allowing. %s >= %s", + new_state, + previous_state_values[-1], + ) return new_state median = statistics.median(previous_state_values) if self.states else 0 diff --git a/custom_components/battery_notes/library.py b/custom_components/battery_notes/library.py index 49c769028..417971b88 100644 --- a/custom_components/battery_notes/library.py +++ b/custom_components/battery_notes/library.py @@ -2,33 +2,64 @@ from __future__ import annotations +import os import json import logging -import os from typing import Any, Final, NamedTuple, cast +from dataclasses import dataclass from homeassistant.core import HomeAssistant +from homeassistant.util.hass_dict import HassKey from homeassistant.helpers.storage import STORAGE_DIR +from .const import DOMAIN from .coordinator import MY_KEY _LOGGER = logging.getLogger(__name__) LIBRARY_DEVICES: Final[str] = "devices" -LIBRARY_MANUFACTURER: Final[str] = "manufacturer" -LIBRARY_MODEL: Final[str] = "model" -LIBRARY_MODEL_MATCH_METHOD: Final[str] = "model_match_method" -LIBRARY_MODEL_ID: Final[str] = "model_id" -LIBRARY_HW_VERSION: Final[str] = "hw_version" -LIBRARY_BATTERY_TYPE: Final[str] = "battery_type" -LIBRARY_BATTERY_QUANTITY: Final[str] = "battery_quantity" +LIBRARY_MANUFACTURER: Final[str] = "manufacturer" +LIBRARY_MODEL: Final[str] = "model" +LIBRARY_MODEL_MATCH_METHOD: Final[str] = "model_match_method" +LIBRARY_MODEL_ID: Final[str] = "model_id" +LIBRARY_HW_VERSION: Final[str] = "hw_version" +LIBRARY_BATTERY_TYPE: Final[str] = "battery_type" +LIBRARY_BATTERY_QUANTITY: Final[str] = "battery_quantity" LIBRARY_MISSING: Final[str] = "##MISSING##" +DATA_LIBRARY: HassKey[Library] = HassKey(f"{DOMAIN}_library") + + +@dataclass(frozen=True, kw_only=True) +class LibraryDevice: + """Class for keeping track of a library device.""" + + manufacturer: str + model: str + battery_type: str + model_match_method: str | None = None + model_id: str | None = None + battery_quantity: int = 1 + hw_version: str | None = None + + @classmethod + def from_json(cls, data: dict[str, Any]) -> LibraryDevice: + """Create LibraryDevice instance from JSON data.""" + return cls( + manufacturer=data[LIBRARY_MANUFACTURER], + model=data[LIBRARY_MODEL], + model_match_method=data.get(LIBRARY_MODEL_MATCH_METHOD), + model_id=data.get(LIBRARY_MODEL_ID), + hw_version=data.get(LIBRARY_HW_VERSION), + battery_type=data[LIBRARY_BATTERY_TYPE], + battery_quantity=data.get(LIBRARY_BATTERY_QUANTITY, 1), + ) + class Library: # pylint: disable=too-few-public-methods """Hold all known battery types.""" - _devices: list = [] + _manufacturer_devices: dict[str, list[LibraryDevice]] = {} def __init__(self, hass: HomeAssistant) -> None: """Init.""" @@ -45,7 +76,9 @@ def _load_library_json(library_file: str) -> dict[str, Any]: # User Library domain_config = self.hass.data.get(MY_KEY) if domain_config and domain_config.user_library != "": - json_user_path = self.hass.config.path(STORAGE_DIR, "battery_notes", domain_config.user_library) + json_user_path = self.hass.config.path( + STORAGE_DIR, "battery_notes", domain_config.user_library + ) _LOGGER.debug("Using user library file at %s", json_user_path) try: @@ -53,16 +86,23 @@ def _load_library_json(library_file: str) -> dict[str, Any]: _load_library_json, json_user_path ) - self._devices = user_json_data["devices"] - _LOGGER.debug( - "Loaded %s user devices", len(user_json_data["devices"]) - ) + for json_device in user_json_data["devices"]: + library_device = LibraryDevice.from_json(json_device) + manufacturer = library_device.manufacturer.casefold() + if manufacturer not in self._manufacturer_devices: + self._manufacturer_devices[manufacturer] = [] + self._manufacturer_devices[manufacturer].append(library_device) + _LOGGER.debug("Loaded %s user devices", len(user_json_data["devices"])) except FileNotFoundError: # Try to move the user library to new location try: - legacy_data_directory = os.path.join(os.path.dirname(__file__), "data") - legacy_json_user_path = os.path.join(legacy_data_directory, domain_config.user_library) + legacy_data_directory = os.path.join( + os.path.dirname(__file__), "data" + ) + legacy_json_user_path = os.path.join( + legacy_data_directory, domain_config.user_library + ) os.makedirs(os.path.dirname(json_user_path), exist_ok=True) os.rename(legacy_json_user_path, json_user_path) @@ -77,7 +117,9 @@ def _load_library_json(library_file: str) -> dict[str, Any]: ) # Default Library - json_default_path = self.hass.config.path(STORAGE_DIR, "battery_notes", "library.json") + json_default_path = self.hass.config.path( + STORAGE_DIR, "battery_notes", "library.json" + ) _LOGGER.debug("Using library file at %s", json_default_path) @@ -85,7 +127,12 @@ def _load_library_json(library_file: str) -> dict[str, Any]: default_json_data = await self.hass.async_add_executor_job( _load_library_json, json_default_path ) - self._devices.extend(default_json_data[LIBRARY_DEVICES]) + for json_device in default_json_data["devices"]: + library_device = LibraryDevice.from_json(json_device) + manufacturer = library_device.manufacturer.casefold() + if manufacturer not in self._manufacturer_devices: + self._manufacturer_devices[manufacturer] = [] + self._manufacturer_devices[manufacturer].append(library_device) _LOGGER.debug( "Loaded %s default devices", len(default_json_data[LIBRARY_DEVICES]) ) @@ -102,19 +149,32 @@ async def get_device_battery_details( ) -> DeviceBatteryDetails | None: """Create a battery details object from the JSON devices data.""" - if self._devices is None: + if not bool(self._manufacturer_devices): return None # Test only - # device_to_find = ModelInfo("Espressif", "m5stack-atom", None, None) + # device_to_find = ModelInfo("Aqara", "Aqara Climate Sensor W100", "8196", None) + # device_to_find = ModelInfo("Google", "Topaz-2.7", None, "Battery") + # device_to_find = ModelInfo("Google", "Topaz-2.7", None, "Wired") + # device_to_find = ModelInfo("Philips", "Hue dimmer switch (929002398602)", None, None) + # device_to_find = ModelInfo("Philips", "Hue dimmer switch", "929002398602", None) + # device_to_find = ModelInfo("Philips", "Hue dimmer switch", "929002398602", "1") # Get all devices matching manufacturer & model matching_devices = None partial_matching_devices = None fully_matching_devices = None + manufacturer_devices = self._manufacturer_devices.get( + device_to_find.manufacturer.casefold(), None + ) + if not manufacturer_devices: + return None + matching_devices = [ - x for x in self._devices if self.device_basic_match(x, device_to_find) + x + for x in manufacturer_devices + if self.device_basic_match(x, device_to_find) ] if matching_devices and len(matching_devices) > 1: @@ -135,91 +195,94 @@ async def get_device_battery_details( if fully_matching_devices and len(fully_matching_devices) > 0: matching_devices = fully_matching_devices - if not matching_devices or len(matching_devices) == 0: + if not matching_devices: + return None + + if len(matching_devices) > 1: return None matched_device = matching_devices[0] + return DeviceBatteryDetails( - manufacturer=matched_device[LIBRARY_MANUFACTURER], - model=matched_device[LIBRARY_MODEL], - model_id=matched_device.get("model_id", ""), - hw_version=matched_device.get(LIBRARY_HW_VERSION, ""), - battery_type=matched_device[LIBRARY_BATTERY_TYPE], - battery_quantity=matched_device.get(LIBRARY_BATTERY_QUANTITY, 1), + manufacturer=matched_device.manufacturer, + model=matched_device.model, + model_id=matched_device.model_id or "", + hw_version=matched_device.hw_version or "", + battery_type=matched_device.battery_type, + battery_quantity=matched_device.battery_quantity, ) - def loaded(self) -> bool: + @property + def is_loaded(self) -> bool: """Library loaded successfully.""" - return self._devices is not None + return bool(self._manufacturer_devices) - def device_basic_match(self, device: dict[str, Any], model_info: ModelInfo) -> bool: + def device_basic_match( + self, library_device: LibraryDevice, device_to_find: ModelInfo + ) -> bool: """Check if device match on manufacturer and model.""" if ( - str(device[LIBRARY_MANUFACTURER] or "").casefold() - != str(model_info.manufacturer or "").casefold() + library_device.manufacturer.casefold() + != device_to_find.manufacturer.casefold() ): return False - if LIBRARY_MODEL_MATCH_METHOD in device: - if device[LIBRARY_MODEL_MATCH_METHOD] == "startswith": + if library_device.model_match_method: + if library_device.model_match_method == "startswith": if ( - str(model_info.model or "") + str(device_to_find.model or "") .casefold() - .startswith(str(device[LIBRARY_MODEL] or "").casefold()) + .startswith(library_device.model.casefold()) ): return True - if device[LIBRARY_MODEL_MATCH_METHOD] == "endswith": + if library_device.model_match_method == "endswith": if ( - str(model_info.model or "") + str(device_to_find.model or "") .casefold() - .endswith(str(device[LIBRARY_MODEL] or "").casefold()) + .endswith(library_device.model.casefold()) ): return True - if device[LIBRARY_MODEL_MATCH_METHOD] == "contains": - if str(model_info.model or "").casefold() in ( - str(device[LIBRARY_MODEL] or "").casefold() + if library_device.model_match_method == "contains": + if str(device_to_find.model or "").casefold() in ( + library_device.model.casefold() ): return True - else: - if ( - str(device[LIBRARY_MODEL] or "").casefold() - == str(model_info.model or "").casefold() - ): - return True + elif ( + library_device.model.casefold() + == str(device_to_find.model or "").casefold() + ): + return True return False def device_partial_match( - self, device: dict[str, Any], model_info: ModelInfo + self, library_device: LibraryDevice, device_to_find: ModelInfo ) -> bool: """Check if device match on hw_version or model_id.""" - if model_info.hw_version is None or model_info.model_id is None: - if ( - device.get(LIBRARY_HW_VERSION, LIBRARY_MISSING).casefold() - == str(model_info.hw_version).casefold() - and device.get(LIBRARY_MODEL_ID, LIBRARY_MISSING).casefold() - == str(model_info.model_id).casefold() - ): - return True - else: - if ( - device.get(LIBRARY_HW_VERSION, LIBRARY_MISSING).casefold() - == str(model_info.hw_version).casefold() - or device.get(LIBRARY_MODEL_ID, LIBRARY_MISSING).casefold() - == str(model_info.model_id).casefold() - ): + if device_to_find.hw_version is None and device_to_find.model_id is None: + return bool( + library_device.hw_version is None and library_device.model_id is None + ) + + if device_to_find.hw_version is None or device_to_find.model_id is None: + if (library_device.hw_version or "").casefold() == str( + device_to_find.hw_version + ).casefold() or (library_device.model_id or "").casefold() == str( + device_to_find.model_id + ).casefold(): return True + return False - def device_full_match(self, device: dict[str, Any], model_info: ModelInfo) -> bool: + def device_full_match( + self, library_device: LibraryDevice, device_to_find: ModelInfo + ) -> bool: """Check if device match on hw_version and model_id.""" - if ( - device.get(LIBRARY_HW_VERSION, LIBRARY_MISSING).casefold() - == str(model_info.hw_version).casefold() - and device.get(LIBRARY_MODEL_ID, LIBRARY_MISSING).casefold() - == str(model_info.model_id).casefold() - ): - return True - return False + return bool( + (library_device.hw_version or "").casefold() + == str(device_to_find.hw_version).casefold() + and (library_device.model_id or "").casefold() + == str(device_to_find.model_id).casefold() + ) class DeviceBatteryDetails(NamedTuple): @@ -235,9 +298,7 @@ class DeviceBatteryDetails(NamedTuple): @property def is_manual(self): """Return whether the device should be discovered or battery type suggested.""" - if self.battery_type.casefold() == "manual".casefold(): - return True - return False + return self.battery_type.casefold() == "manual".casefold() @property def battery_type_and_quantity(self): diff --git a/custom_components/battery_notes/library_updater.py b/custom_components/battery_notes/library_updater.py index a23bd305c..fe9ed688d 100644 --- a/custom_components/battery_notes/library_updater.py +++ b/custom_components/battery_notes/library_updater.py @@ -2,27 +2,41 @@ from __future__ import annotations -import json -import logging import os +import json import shutil import socket -from datetime import datetime, timedelta +import logging from typing import Any +from datetime import datetime, timedelta import aiohttp import async_timeout + from homeassistant.core import HomeAssistant, callback +from homeassistant.const import CONTENT_TYPE_JSON from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_track_utc_time_change from homeassistant.helpers.storage import STORAGE_DIR +from homeassistant.helpers.aiohttp_client import async_get_clientsession -from .coordinator import MY_KEY, BatteryNotesDomainConfig +from .const import ( + VERSION, + DEFAULT_LIBRARY_URL, +) +from .library import DATA_LIBRARY from .discovery import DiscoveryManager +from .coordinator import MY_KEY, BatteryNotesDomainConfig _LOGGER = logging.getLogger(__name__) +HEADERS = { + "User-Agent": f"BatteryNotes/{VERSION}", + "Content-Type": CONTENT_TYPE_JSON, + "Accept-Encoding": "gzip", +} + + class LibraryUpdaterClientError(Exception): """Exception to indicate a general API error.""" @@ -42,25 +56,29 @@ def __init__(self, hass: HomeAssistant): if not domain_config: domain_config = BatteryNotesDomainConfig() - library_url = domain_config.library_url - schema_url = domain_config.schema_url - - self._client = LibraryUpdaterClient(library_url=library_url, schema_url=schema_url, session=async_get_clientsession(hass)) + self._client = LibraryUpdaterClient(session=async_get_clientsession(hass)) # Fire the library check every 24 hours from just before now refresh_time = datetime.now() - timedelta(hours=0, minutes=1) async_track_utc_time_change( - hass, self.timer_update, hour=refresh_time.hour, minute=refresh_time.minute, second=refresh_time.second, local=True + hass, + self.timer_update, + hour=refresh_time.hour, + minute=refresh_time.minute, + second=refresh_time.second, + local=True, ) @callback - async def timer_update(self, now: datetime): + async def timer_update(self, now: datetime) -> None: # noqa: ARG002 """Need to update the library.""" if await self.time_to_update_library(23) is False: return await self.get_library_updates() + await self.hass.data[DATA_LIBRARY].load_libraries() + domain_config = self.hass.data[MY_KEY] if domain_config and domain_config.enable_autodiscovery: @@ -71,7 +89,6 @@ async def timer_update(self, now: datetime): @callback async def get_library_updates(self, startup: bool = False) -> None: - # pylint: disable=unused-argument """Make a call to get the latest library.json.""" def _update_library_json(library_file: str, content: str) -> None: @@ -86,7 +103,9 @@ def _update_library_json(library_file: str, content: str) -> None: content = await self._client.async_get_data() if self.validate_json(content): - json_path = self.hass.config.path(STORAGE_DIR, "battery_notes", "library.json") + json_path = self.hass.config.path( + STORAGE_DIR, "battery_notes", "library.json" + ) await self.hass.async_add_executor_job( _update_library_json, json_path, content @@ -102,15 +121,15 @@ def _update_library_json(library_file: str, content: str) -> None: except LibraryUpdaterClientError: if not startup: - _LOGGER.warning( - "Unable to update library, will retry later." - ) + _LOGGER.warning("Unable to update library, will retry later.") async def copy_schema(self): """Copy schema file to storage to be relative to downloaded library.""" install_schema_path = os.path.join(os.path.dirname(__file__), "schema.json") - storage_schema_path = self.hass.config.path(STORAGE_DIR, "battery_notes", "schema.json") + storage_schema_path = self.hass.config.path( + STORAGE_DIR, "battery_notes", "schema.json" + ) os.makedirs(os.path.dirname(storage_schema_path), exist_ok=True) await self.hass.async_add_executor_job( shutil.copyfile, @@ -126,9 +145,7 @@ async def time_to_update_library(self, hours: int) -> bool: return True if library_last_update := self.hass.data[MY_KEY].library_last_update: - time_since_last_update = ( - datetime.now() - library_last_update - ) + time_since_last_update = datetime.now() - library_last_update time_difference_in_hours = time_since_last_update / timedelta(hours=1) @@ -161,24 +178,23 @@ class LibraryUpdaterClient: def __init__( self, - library_url: str, - schema_url: str, session: aiohttp.ClientSession, ) -> None: """Client to get latest library file from GitHub.""" - self._library_url = library_url - self._schema_url = schema_url self._session = session async def async_get_data(self) -> Any: """Get data from the API.""" - _LOGGER.debug(f"Updating library from {self._library_url}") - return await self._api_wrapper(method="get", url=self._library_url) + _LOGGER.debug("Updating library from %s", DEFAULT_LIBRARY_URL) + return await self._api_wrapper( + method="get", url=DEFAULT_LIBRARY_URL, headers=HEADERS + ) async def _api_wrapper( self, method: str, url: str, + headers: dict[str, str], ) -> Any: """Get information from the API.""" try: @@ -187,6 +203,7 @@ async def _api_wrapper( method=method, url=url, allow_redirects=True, + headers=headers, ) # response.raise_for_status() return await response.text() diff --git a/custom_components/battery_notes/manifest.json b/custom_components/battery_notes/manifest.json index 5092f9959..10ebc1fb1 100644 --- a/custom_components/battery_notes/manifest.json +++ b/custom_components/battery_notes/manifest.json @@ -6,8 +6,8 @@ ], "config_flow": true, "documentation": "https://andrew-codechimp.github.io/HA-Battery-Notes/", - "integration_type": "device", + "integration_type": "service", "iot_class": "calculated", "issue_tracker": "https://github.com/andrew-codechimp/ha-battery-notes/issues", - "version": "2.0.0-dev" + "version": "3.0.0-dev" } \ No newline at end of file diff --git a/custom_components/battery_notes/repairs.py b/custom_components/battery_notes/repairs.py index 70372687f..8f167fc12 100644 --- a/custom_components/battery_notes/repairs.py +++ b/custom_components/battery_notes/repairs.py @@ -5,13 +5,15 @@ from typing import cast import voluptuous as vol + from homeassistant import data_entry_flow -from homeassistant.components.repairs import RepairsFlow from homeassistant.core import HomeAssistant from homeassistant.helpers import issue_registry as ir +from homeassistant.components.repairs import RepairsFlow REQUIRED_KEYS = ("entry_id", "device_id", "source_entity_id") + class MissingDeviceRepairFlow(RepairsFlow): """Handler for an issue fixing flow.""" @@ -20,24 +22,27 @@ def __init__(self, data: dict[str, str | int | float | None] | None) -> None: if not data or any(key not in data for key in REQUIRED_KEYS): raise ValueError("Missing data") self.entry_id = cast(str, data["entry_id"]) + self.subentry_id = cast(str, data["subentry_id"]) self.device_id = cast(str, data["device_id"]) self.source_entity_id = cast(str, data["source_entity_id"]) async def async_step_init( - self, user_input: dict[str, str] | None = None + self, + user_input: dict[str, str] | None = None, # noqa: ARG002 ) -> data_entry_flow.FlowResult: """Handle the first step of a fix flow.""" - return await (self.async_step_confirm()) + return await self.async_step_confirm() async def async_step_confirm( self, user_input: dict[str, str] | None = None ) -> data_entry_flow.FlowResult: """Handle the confirm step of a fix flow.""" if user_input is not None: - await self.hass.config_entries.async_remove(self.entry_id) + if entry := self.hass.config_entries.async_get_entry(self.entry_id): + self.hass.config_entries.async_remove_subentry(entry, self.subentry_id) - return self.async_create_entry(title="", data={}) + return self.async_create_entry(data={}) issue_registry = ir.async_get(self.hass) description_placeholders = None @@ -47,17 +52,18 @@ async def async_step_confirm( return self.async_show_form( step_id="confirm", data_schema=vol.Schema({}), - description_placeholders=description_placeholders + description_placeholders=description_placeholders, ) async def async_create_fix_flow( - hass: HomeAssistant, + hass: HomeAssistant, # noqa: ARG001 issue_id: str, data: dict[str, str | int | float | None] | None, ) -> RepairsFlow: """Create flow.""" if issue_id.startswith("missing_device_"): assert data + return MissingDeviceRepairFlow(data) raise ValueError(f"unknown repair {issue_id}") diff --git a/custom_components/battery_notes/sensor.py b/custom_components/battery_notes/sensor.py index 8c2242e2a..caad5a076 100644 --- a/custom_components/battery_notes/sensor.py +++ b/custom_components/battery_notes/sensor.py @@ -3,78 +3,74 @@ from __future__ import annotations import logging -from collections.abc import Mapping -from dataclasses import dataclass -from datetime import datetime from typing import Any +from datetime import datetime +from dataclasses import dataclass +from collections.abc import Mapping import voluptuous as vol -from homeassistant.components.sensor import ( - PLATFORM_SCHEMA, - RestoreSensor, - SensorDeviceClass, - SensorEntity, - SensorEntityDescription, - SensorStateClass, -) -from homeassistant.config_entries import ConfigEntry + +from homeassistant.core import Event, HomeAssistant, callback from homeassistant.const import ( - CONF_DEVICE_ID, CONF_NAME, PERCENTAGE, - STATE_UNAVAILABLE, STATE_UNKNOWN, -) -from homeassistant.core import Event, HomeAssistant, callback, split_entity_id -from homeassistant.helpers import ( - config_validation as cv, -) -from homeassistant.helpers import ( - device_registry as dr, + CONF_DEVICE_ID, + STATE_UNAVAILABLE, ) from homeassistant.helpers import ( entity_registry as er, -) -from homeassistant.helpers.entity import DeviceInfo, EntityCategory -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.entity_registry import ( - EVENT_ENTITY_REGISTRY_UPDATED, + config_validation as cv, ) from homeassistant.helpers.event import ( EventStateChangedData, EventStateReportedData, - async_track_entity_registry_updated_event, async_track_state_change_event, async_track_state_report_event, ) -from homeassistant.helpers.reload import async_setup_reload_service +from homeassistant.config_entries import ConfigSubentry +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.typing import StateType -from homeassistant.helpers.update_coordinator import ( - CoordinatorEntity, +from homeassistant.components.sensor import ( + PLATFORM_SCHEMA, + SensorEntity, + RestoreSensor, + SensorStateClass, + SensorDeviceClass, + SensorEntityDescription, +) +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback +from homeassistant.helpers.entity_registry import ( + EVENT_ENTITY_REGISTRY_UPDATED, ) -from .common import utcnow_no_timezone, validate_is_float from .const import ( - ATTR_BATTERY_LAST_REPLACED, - ATTR_BATTERY_LAST_REPORTED, - ATTR_BATTERY_LAST_REPORTED_LEVEL, - ATTR_BATTERY_LOW, - ATTR_BATTERY_LOW_THRESHOLD, - ATTR_BATTERY_QUANTITY, - ATTR_BATTERY_TYPE, - ATTR_BATTERY_TYPE_AND_QUANTITY, + DOMAIN, ATTR_DEVICE_ID, + ATTR_BATTERY_LOW, ATTR_DEVICE_NAME, + ATTR_BATTERY_TYPE, + CONF_BATTERY_TYPE, + CONF_ROUND_BATTERY, + CONF_ENABLE_REPLACED, + ATTR_BATTERY_QUANTITY, ATTR_SOURCE_ENTITY_ID, CONF_BATTERY_QUANTITY, - CONF_BATTERY_TYPE, CONF_SOURCE_ENTITY_ID, - DOMAIN, - PLATFORMS, + SUBENTRY_BATTERY_NOTE, + CONF_ADVANCED_SETTINGS, + ATTR_BATTERY_LAST_REPLACED, + ATTR_BATTERY_LAST_REPORTED, + ATTR_BATTERY_LOW_THRESHOLD, + ATTR_BATTERY_TYPE_AND_QUANTITY, + ATTR_BATTERY_LAST_REPORTED_LEVEL, ) -from .coordinator import MY_KEY, BatteryNotesConfigEntry, BatteryNotesCoordinator -from .entity import ( - BatteryNotesEntityDescription, +from .common import validate_is_float, utcnow_no_timezone +from .entity import BatteryNotesEntity, BatteryNotesEntityDescription +from .coordinator import ( + MY_KEY, + BatteryNotesConfigEntry, + BatteryNotesSubentryCoordinator, ) @@ -101,148 +97,230 @@ class BatteryNotesSensorEntityDescription( _LOGGER = logging.getLogger(__name__) -@callback -def async_add_to_device(hass: HomeAssistant, entry: BatteryNotesConfigEntry) -> str | None: - """Add our config entry to the device.""" - device_registry = dr.async_get(hass) - - device_id = entry.data.get(CONF_DEVICE_ID) - - if device_id: - if device_registry.async_get(device_id): - device_registry.async_update_device( - device_id, add_config_entry_id=entry.entry_id - ) - return device_id - return None - - async def async_setup_entry( hass: HomeAssistant, config_entry: BatteryNotesConfigEntry, - async_add_entities: AddEntitiesCallback, + async_add_entities: AddConfigEntryEntitiesCallback, ) -> None: """Initialize Battery Type config entry.""" - entity_registry = er.async_get(hass) - device_registry = dr.async_get(hass) - device_id = config_entry.data.get(CONF_DEVICE_ID, None) + for subentry in config_entry.subentries.values(): + if subentry.subentry_type != SUBENTRY_BATTERY_NOTE: + continue - async def async_registry_updated(event: Event[er.EventEntityRegistryUpdatedData]) -> None: - """Handle entity registry update.""" - data = event.data - if data["action"] == "remove": - await hass.config_entries.async_remove(config_entry.entry_id) + assert config_entry.runtime_data.subentry_coordinators + coordinator = config_entry.runtime_data.subentry_coordinators.get( + subentry.subentry_id + ) + assert coordinator - if data["action"] != "update": - return + if coordinator.is_orphaned: + _LOGGER.debug( + "Skipping sensor creation for orphaned subentry: %s", + subentry.title, + ) + continue + + type_sensor_entity_description = BatteryNotesSensorEntityDescription( + unique_id_suffix="_battery_type", + key="battery_type", + translation_key="battery_type", + entity_category=EntityCategory.DIAGNOSTIC, + entity_type="sensor", + ) - if "entity_id" in data["changes"]: - # Entity_id replaced, reload the config entry - await hass.config_entries.async_reload(config_entry.entry_id) + last_replaced_sensor_entity_description = BatteryNotesSensorEntityDescription( + unique_id_suffix="_battery_last_replaced", + key="battery_last_replaced", + translation_key="battery_last_replaced", + entity_category=EntityCategory.DIAGNOSTIC, + device_class=SensorDeviceClass.TIMESTAMP, + entity_registry_enabled_default=config_entry.options[ + CONF_ADVANCED_SETTINGS + ].get(CONF_ENABLE_REPLACED, True), + entity_type="sensor", + ) - if device_id and "device_id" in data["changes"]: - # If the tracked battery note is no longer in the device, remove our config entry - # from the device - if ( - not (entity_entry := entity_registry.async_get(data["entity_id"])) - or not device_registry.async_get(device_id) - or entity_entry.device_id == device_id - ): - # No need to do any cleanup - return + battery_plus_sensor_entity_description = BatteryNotesSensorEntityDescription( + unique_id_suffix="_battery_plus", + key="battery_plus", + translation_key="battery_plus", + device_class=SensorDeviceClass.BATTERY, + suggested_display_precision=0 + if config_entry.options[CONF_ADVANCED_SETTINGS].get( + CONF_ROUND_BATTERY, True + ) + else 1, + entity_type="sensor", + require_device=True, + ) - device_registry.async_update_device( - device_id, remove_config_entry_id=config_entry.entry_id + entities = [ + BatteryNotesTypeSensor( + hass, + config_entry, + subentry, + type_sensor_entity_description, + coordinator, + f"{subentry.unique_id}{type_sensor_entity_description.unique_id_suffix}", + ), + BatteryNotesLastReplacedSensor( + hass, + config_entry, + subentry, + last_replaced_sensor_entity_description, + coordinator, + last_replaced_sensor_entity_description, + f"{subentry.unique_id}{last_replaced_sensor_entity_description.unique_id_suffix}", + ), + ] + + if coordinator.wrapped_battery is not None: + entities.append( + BatteryNotesBatteryPlusSensor( + hass, + config_entry, + subentry, + battery_plus_sensor_entity_description, + coordinator, + f"{subentry.unique_id}{battery_plus_sensor_entity_description.unique_id_suffix}", + config_entry.options[CONF_ADVANCED_SETTINGS].get( + CONF_ENABLE_REPLACED, True + ), + config_entry.options[CONF_ADVANCED_SETTINGS].get( + CONF_ROUND_BATTERY, False + ), + ) ) - config_entry.async_on_unload( - async_track_entity_registry_updated_event( - hass, config_entry.entry_id, async_registry_updated + async_add_entities( + entities, + config_subentry_id=subentry.subentry_id, ) - ) - coordinator = config_entry.runtime_data.coordinator - assert(coordinator) - if not coordinator.fake_device: - device_id = async_add_to_device(hass, config_entry) - - if not device_id: - return +class BatteryNotesTypeSensor(BatteryNotesEntity, RestoreSensor): + """Represents a battery note type sensor.""" - await coordinator.async_refresh() + _attr_should_poll = False + entity_description: BatteryNotesSensorEntityDescription + _unrecorded_attributes = frozenset({ATTR_BATTERY_QUANTITY, ATTR_BATTERY_TYPE}) - domain_config = hass.data[MY_KEY] + def __init__( # noqa: PLR0913 + self, + hass, + config_entry: BatteryNotesConfigEntry, # noqa: ARG002 + subentry: ConfigSubentry, # noqa: ARG002 + entity_description: BatteryNotesEntityDescription, + coordinator: BatteryNotesSubentryCoordinator, + unique_id: str, + ) -> None: + # pylint: disable=unused-argument + """Initialize the sensor.""" + super().__init__( + hass=hass, entity_description=entity_description, coordinator=coordinator + ) - battery_plus_sensor_entity_description = BatteryNotesSensorEntityDescription( - unique_id_suffix="_battery_plus", - key="battery_plus", - translation_key="battery_plus", - device_class=SensorDeviceClass.BATTERY, - suggested_display_precision=0 if domain_config.round_battery else 1, - ) + self._attr_unique_id = unique_id - type_sensor_entity_description = BatteryNotesSensorEntityDescription( - unique_id_suffix="", # battery_type has uniqueId set to entityId in V1, never add a suffix - key="battery_type", - translation_key="battery_type", - entity_category=EntityCategory.DIAGNOSTIC, - ) + self._battery_type = coordinator.battery_type + self._battery_quantity = coordinator.battery_quantity - last_replaced_sensor_entity_description = BatteryNotesSensorEntityDescription( - unique_id_suffix="_battery_last_replaced", - key="battery_last_replaced", - translation_key="battery_last_replaced", - entity_category=EntityCategory.DIAGNOSTIC, - device_class=SensorDeviceClass.TIMESTAMP, - entity_registry_enabled_default=domain_config.enable_replaced, - ) + async def async_added_to_hass(self) -> None: + """Handle added to Hass.""" + await super().async_added_to_hass() + state = await self.async_get_last_sensor_data() + if state: + self._attr_native_value = state.native_value - entities = [ - BatteryNotesTypeSensor( - hass, - config_entry, - coordinator, - type_sensor_entity_description, - f"{config_entry.entry_id}{type_sensor_entity_description.unique_id_suffix}", - ), - BatteryNotesLastReplacedSensor( - hass, - config_entry, - coordinator, - last_replaced_sensor_entity_description, - f"{config_entry.entry_id}{last_replaced_sensor_entity_description.unique_id_suffix}", - ), - ] - - if coordinator.wrapped_battery is not None: - entities.append( - BatteryNotesBatteryPlusSensor( - hass, - config_entry, - coordinator, - battery_plus_sensor_entity_description, - f"{config_entry.entry_id}{battery_plus_sensor_entity_description.unique_id_suffix}", - domain_config.enable_replaced, - domain_config.round_battery, + # Update entity options, this is needed for legacy v1 support + registry = er.async_get(self.hass) + if registry.async_get(self.entity_id) is not None: + registry.async_update_entity_options( + self.entity_id, + DOMAIN, + { + "entity_id": self._attr_unique_id, + }, ) + + @property + def native_value(self) -> str: + """Return the native value of the sensor.""" + return self.coordinator.battery_type_and_quantity + + @property + def extra_state_attributes(self) -> dict[str, Any] | None: + """Return the state attributes of the battery type.""" + + attrs = { + ATTR_BATTERY_QUANTITY: self.coordinator.battery_quantity, + ATTR_BATTERY_TYPE: self.coordinator.battery_type, + } + + super_attrs = super().extra_state_attributes + if super_attrs: + attrs.update(super_attrs) + return attrs + + +class BatteryNotesLastReplacedSensor(BatteryNotesEntity, SensorEntity): + """Represents a battery note sensor.""" + + _attr_should_poll = False + entity_description: BatteryNotesSensorEntityDescription + + def __init__( # noqa: PLR0913 + self, + hass, + config_entry: BatteryNotesConfigEntry, # noqa: ARG002 + subentry: ConfigSubentry, # noqa: ARG002 + entity_description: BatteryNotesEntityDescription, + coordinator: BatteryNotesSubentryCoordinator, + description: BatteryNotesSensorEntityDescription, + unique_id: str, + ) -> None: + # pylint: disable=unused-argument + """Initialize the sensor.""" + super().__init__( + hass=hass, entity_description=entity_description, coordinator=coordinator ) - async_add_entities(entities) + self._attr_device_class = description.device_class + self._attr_unique_id = unique_id + self._device_id = coordinator.device_id + self._source_entity_id = coordinator.source_entity_id + self._native_value: datetime | None = None + self._set_native_value(log_on_error=False) -async def async_setup_platform( - hass: HomeAssistant, -) -> None: - """Set up the battery note sensor.""" + async def async_added_to_hass(self) -> None: + """Handle added to Hass.""" + await super().async_added_to_hass() - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) + def _set_native_value(self, log_on_error=True): # noqa: ARG002 + if last_replaced := self.coordinator.last_replaced: + self._native_value = last_replaced + return True + return False -class BatteryNotesBatteryPlusSensor( - RestoreSensor, SensorEntity, CoordinatorEntity[BatteryNotesCoordinator] -): + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + + if last_replaced := self.coordinator.last_replaced: + self._native_value = last_replaced + + self.async_write_ha_state() + + @property + def native_value(self) -> datetime | None: + """Return the native value of the sensor.""" + return self._native_value + + +class BatteryNotesBatteryPlusSensor(BatteryNotesEntity, RestoreSensor): """Represents a battery plus type sensor.""" _attr_should_poll = False @@ -263,48 +341,27 @@ class BatteryNotesBatteryPlusSensor( } ) - def __init__( + entity_description: BatteryNotesSensorEntityDescription + + def __init__( # noqa: PLR0913 self, hass: HomeAssistant, - config_entry: ConfigEntry, - coordinator: BatteryNotesCoordinator, - description: BatteryNotesSensorEntityDescription, + config_entry: BatteryNotesConfigEntry, + subentry: ConfigSubentry, # noqa: ARG002 + entity_description: BatteryNotesEntityDescription, + coordinator: BatteryNotesSubentryCoordinator, unique_id: str, enable_replaced: bool, round_battery: bool, ) -> None: + # pylint: disable=unused-argument """Initialize the sensor.""" - super().__init__(coordinator) - - device_registry = dr.async_get(hass) + super().__init__( + hass=hass, entity_description=entity_description, coordinator=coordinator + ) self.config_entry = config_entry - self.coordinator = coordinator - - self._attr_has_entity_name = True - if coordinator.source_entity_id and not coordinator.device_id: - self._attr_translation_placeholders = { - "device_name": coordinator.device_name + " " - } - self.entity_id = ( - f"sensor.{coordinator.device_name.lower()}_{description.key}" - ) - elif coordinator.source_entity_id and coordinator.device_id: - source_entity_domain, source_object_id = split_entity_id( - coordinator.source_entity_id - ) - self._attr_translation_placeholders = { - "device_name": coordinator.source_entity_name + " " - } - self.entity_id = f"sensor.{source_object_id}_{description.key}" - else: - self._attr_translation_placeholders = {"device_name": ""} - self.entity_id = ( - f"sensor.{coordinator.device_name.lower()}_{description.key}" - ) - - self.entity_description = description self._attr_unique_id = unique_id self.enable_replaced = enable_replaced self.round_battery = round_battery @@ -312,16 +369,10 @@ def __init__( self._device_id = coordinator.device_id self._source_entity_id = coordinator.source_entity_id - if coordinator.device_id and ( - device_entry := device_registry.async_get(coordinator.device_id) - ): - self._attr_device_info = DeviceInfo( - connections=device_entry.connections, - identifiers=device_entry.identifiers, - ) - entity_category = ( - coordinator.wrapped_battery.entity_category if coordinator.wrapped_battery else None + coordinator.wrapped_battery.entity_category + if coordinator.wrapped_battery + else None ) self._attr_entity_category = entity_category @@ -333,7 +384,8 @@ def __init__( @callback async def async_state_changed_listener( - self, event: Event[EventStateChangedData] | None = None + self, + event: Event[EventStateChangedData] | None = None, # noqa: ARG002 ) -> None: # pylint: disable=unused-argument """Handle child updates.""" @@ -355,10 +407,18 @@ async def async_state_changed_listener( ] or not validate_is_float(wrapped_battery_state.state) ): - _LOGGER.debug("Sensor.py -> wrapped_battery_state: %s", wrapped_battery_state) + _LOGGER.debug( + "Sensor.py -> wrapped_battery_state: %s", wrapped_battery_state + ) if wrapped_battery_state: - _LOGGER.debug("Sensor.py -> wrapped_battery_state.state: <%s>", wrapped_battery_state.state) - _LOGGER.debug("Sensor.py -> validate_is_float: <%s>", validate_is_float(wrapped_battery_state.state)) + _LOGGER.debug( + "Sensor.py -> wrapped_battery_state.state: <%s>", + wrapped_battery_state.state, + ) + _LOGGER.debug( + "Sensor.py -> validate_is_float: <%s>", + validate_is_float(wrapped_battery_state.state), + ) self._attr_native_value = None self._attr_available = False @@ -377,9 +437,9 @@ async def async_state_changed_listener( @callback async def async_state_reported_listener( - self, event: Event[EventStateReportedData] | None = None + self, + event: Event[EventStateReportedData] | None = None, # noqa: ARG002 ) -> None: - # pylint: disable=unused-argument """Handle child updates.""" if not self.coordinator.wrapped_battery: @@ -431,7 +491,9 @@ async def _register_entity_id_change_listener( """Listen for battery entity_id changes and update battery_plus.""" @callback - async def _entity_rename_listener(event: Event[er.EventEntityRegistryUpdatedData]) -> None: + async def _entity_rename_listener( + event: Event[er.EventEntityRegistryUpdatedData], + ) -> None: """Handle renaming of the entity.""" new_entity_id = event.data["entity_id"] @@ -555,15 +617,14 @@ async def _async_state_reported_listener( self.coordinator.wrapped_battery.entity_id, hidden_by=er.RegistryEntryHider.INTEGRATION, ) - else: - if ( - self.coordinator.wrapped_battery - and self.coordinator.wrapped_battery.hidden_by - == er.RegistryEntryHider.INTEGRATION - ): - registry.async_update_entity( - self.coordinator.wrapped_battery.entity_id, hidden_by=None - ) + elif ( + self.coordinator.wrapped_battery + and self.coordinator.wrapped_battery.hidden_by + == er.RegistryEntryHider.INTEGRATION + ): + registry.async_update_entity( + self.coordinator.wrapped_battery.entity_id, hidden_by=None + ) self.async_on_remove( self.coordinator.async_add_listener(self._handle_coordinator_update) @@ -613,195 +674,3 @@ def extra_state_attributes(self) -> dict[str, Any] | None: def native_value(self) -> StateType | Any | datetime: """Return the value reported by the sensor.""" return self._attr_native_value - - -class BatteryNotesTypeSensor(RestoreSensor, SensorEntity): - """Represents a battery note type sensor.""" - - _attr_should_poll = False - entity_description: BatteryNotesSensorEntityDescription - _unrecorded_attributes = frozenset({ATTR_BATTERY_QUANTITY, ATTR_BATTERY_TYPE}) - - def __init__( - self, - hass, - config_entry: ConfigEntry, - coordinator: BatteryNotesCoordinator, - description: BatteryNotesSensorEntityDescription, - unique_id: str, - ) -> None: - # pylint: disable=unused-argument - """Initialize the sensor.""" - super().__init__() - - device_registry = dr.async_get(hass) - - self.coordinator = coordinator - - self._attr_has_entity_name = True - - if coordinator.source_entity_id and not coordinator.device_id: - self._attr_translation_placeholders = { - "device_name": coordinator.device_name + " " - } - self.entity_id = ( - f"sensor.{coordinator.device_name.lower()}_{description.key}" - ) - elif coordinator.source_entity_id and coordinator.device_id: - source_entity_domain, source_object_id = split_entity_id( - coordinator.source_entity_id - ) - self._attr_translation_placeholders = { - "device_name": coordinator.source_entity_name + " " - } - self.entity_id = f"sensor.{source_object_id}_{description.key}" - else: - self._attr_translation_placeholders = {"device_name": ""} - self.entity_id = ( - f"sensor.{coordinator.device_name.lower()}_{description.key}" - ) - - self.entity_description = description - self._attr_unique_id = unique_id - self._device_id = coordinator.device_id - self._source_entity_id = coordinator.source_entity_id - - if coordinator.device_id and ( - device_entry := device_registry.async_get(coordinator.device_id) - ): - self._attr_device_info = DeviceInfo( - connections=device_entry.connections, - identifiers=device_entry.identifiers, - ) - - self._battery_type = coordinator.battery_type - self._battery_quantity = coordinator.battery_quantity - - async def async_added_to_hass(self) -> None: - """Handle added to Hass.""" - await super().async_added_to_hass() - state = await self.async_get_last_sensor_data() - if state: - self._attr_native_value = state.native_value - - # Update entity options - registry = er.async_get(self.hass) - if registry.async_get(self.entity_id) is not None: - registry.async_update_entity_options( - self.entity_id, - DOMAIN, - { - "entity_id": self._attr_unique_id, - }, - ) - - @property - def native_value(self) -> str: - """Return the native value of the sensor.""" - return self.coordinator.battery_type_and_quantity - - @property - def extra_state_attributes(self) -> dict[str, Any] | None: - """Return the state attributes of the battery type.""" - - attrs = { - ATTR_BATTERY_QUANTITY: self.coordinator.battery_quantity, - ATTR_BATTERY_TYPE: self.coordinator.battery_type, - } - - super_attrs = super().extra_state_attributes - if super_attrs: - attrs.update(super_attrs) - return attrs - - -class BatteryNotesLastReplacedSensor( - SensorEntity, CoordinatorEntity[BatteryNotesCoordinator] -): - """Represents a battery note sensor.""" - - _attr_should_poll = False - entity_description: BatteryNotesSensorEntityDescription - - def __init__( - self, - hass, - config_entry: ConfigEntry, - coordinator: BatteryNotesCoordinator, - description: BatteryNotesSensorEntityDescription, - unique_id: str, - ) -> None: - # pylint: disable=unused-argument - """Initialize the sensor.""" - super().__init__(coordinator) - - self.coordinator = coordinator - - self._attr_has_entity_name = True - - if coordinator.source_entity_id and not coordinator.device_id: - self._attr_translation_placeholders = { - "device_name": coordinator.device_name + " " - } - self.entity_id = ( - f"sensor.{coordinator.device_name.lower()}_{description.key}" - ) - elif coordinator.source_entity_id and coordinator.device_id: - source_entity_domain, source_object_id = split_entity_id( - coordinator.source_entity_id - ) - self._attr_translation_placeholders = { - "device_name": coordinator.source_entity_name + " " - } - self.entity_id = f"sensor.{source_object_id}_{description.key}" - else: - self._attr_translation_placeholders = {"device_name": ""} - self.entity_id = ( - f"sensor.{coordinator.device_name.lower()}_{description.key}" - ) - - self._attr_device_class = description.device_class - self._attr_unique_id = unique_id - self._device_id = coordinator.device_id - self._source_entity_id = coordinator.source_entity_id - self.entity_description = description - self._native_value: datetime | None = None - - self._set_native_value(log_on_error=False) - - device_registry = dr.async_get(hass) - - if coordinator.device_id and ( - device_entry := device_registry.async_get(coordinator.device_id) - ): - self._attr_device_info = DeviceInfo( - connections=device_entry.connections, - identifiers=device_entry.identifiers, - ) - - async def async_added_to_hass(self) -> None: - """Handle added to Hass.""" - await super().async_added_to_hass() - - def _set_native_value(self, log_on_error=True): - # pylint: disable=unused-argument - - if last_replaced := self.coordinator.last_replaced: - self._native_value = last_replaced - - return True - return False - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - - if last_replaced := self.coordinator.last_replaced: - self._native_value = last_replaced - - self.async_write_ha_state() - - @property - def native_value(self) -> datetime | None: - """Return the native value of the sensor.""" - return self._native_value diff --git a/custom_components/battery_notes/services.py b/custom_components/battery_notes/services.py index 411c18be3..444db78fb 100644 --- a/custom_components/battery_notes/services.py +++ b/custom_components/battery_notes/services.py @@ -1,52 +1,53 @@ """Define services for the Battery Notes integration.""" import logging -from datetime import datetime from typing import cast +from datetime import datetime from homeassistant.core import ( - HomeAssistant, ServiceCall, + HomeAssistant, ServiceResponse, callback, ) -from homeassistant.helpers import device_registry as dr -from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.exceptions import HomeAssistantError -from .common import utcnow_no_timezone from .const import ( - ATTR_BATTERY_LAST_REPLACED, - ATTR_BATTERY_LAST_REPORTED, - ATTR_BATTERY_LAST_REPORTED_DAYS, - ATTR_BATTERY_LAST_REPORTED_LEVEL, - ATTR_BATTERY_LEVEL, - ATTR_BATTERY_LOW, - ATTR_BATTERY_LOW_THRESHOLD, - ATTR_BATTERY_QUANTITY, - ATTR_BATTERY_THRESHOLD_REMINDER, - ATTR_BATTERY_TYPE, - ATTR_BATTERY_TYPE_AND_QUANTITY, + DOMAIN, ATTR_DEVICE_ID, + ATTR_BATTERY_LOW, ATTR_DEVICE_NAME, - ATTR_PREVIOUS_BATTERY_LEVEL, + ATTR_BATTERY_TYPE, + ATTR_BATTERY_LEVEL, + ATTR_BATTERY_QUANTITY, ATTR_SOURCE_ENTITY_ID, - DOMAIN, - EVENT_BATTERY_NOT_REPORTED, EVENT_BATTERY_REPLACED, EVENT_BATTERY_THRESHOLD, SERVICE_BATTERY_REPLACED, - SERVICE_BATTERY_REPLACED_SCHEMA, - SERVICE_CHECK_BATTERY_LAST_REPORTED, - SERVICE_CHECK_BATTERY_LAST_REPORTED_SCHEMA, SERVICE_CHECK_BATTERY_LOW, + ATTR_BATTERY_LAST_REPLACED, + ATTR_BATTERY_LAST_REPORTED, + ATTR_BATTERY_LOW_THRESHOLD, + EVENT_BATTERY_NOT_REPORTED, + ATTR_PREVIOUS_BATTERY_LEVEL, + ATTR_BATTERY_TYPE_AND_QUANTITY, + ATTR_BATTERY_LAST_REPORTED_DAYS, + ATTR_BATTERY_THRESHOLD_REMINDER, + SERVICE_BATTERY_REPLACED_SCHEMA, SERVICE_DATA_DATE_TIME_REPLACED, SERVICE_DATA_DAYS_LAST_REPORTED, + ATTR_BATTERY_LAST_REPORTED_LEVEL, + SERVICE_CHECK_BATTERY_LAST_REPORTED, + SERVICE_CHECK_BATTERY_LAST_REPORTED_SCHEMA, ) +from .common import utcnow_no_timezone from .coordinator import BatteryNotesConfigEntry _LOGGER = logging.getLogger(__name__) + @callback def async_setup_services(hass: HomeAssistant) -> None: """Set up the services for the Mastodon integration.""" @@ -72,16 +73,14 @@ def async_setup_services(hass: HomeAssistant) -> None: ) -async def _async_battery_replaced(call: ServiceCall) -> ServiceResponse: +async def _async_battery_replaced(call: ServiceCall) -> ServiceResponse: # noqa: PLR0912 """Handle the service call.""" device_id = call.data.get(ATTR_DEVICE_ID, "") source_entity_id = call.data.get(ATTR_SOURCE_ENTITY_ID, "") datetime_replaced_entry = call.data.get(SERVICE_DATA_DATE_TIME_REPLACED) if datetime_replaced_entry: - datetime_replaced = dt_util.as_utc(datetime_replaced_entry).replace( - tzinfo=None - ) + datetime_replaced = dt_util.as_utc(datetime_replaced_entry).replace(tzinfo=None) else: datetime_replaced = utcnow_no_timezone() @@ -97,92 +96,114 @@ async def _async_battery_replaced(call: ServiceCall) -> ServiceResponse: ) return None - # entity_id is the associated entity, now need to find the config entry for battery notes - config_entry: BatteryNotesConfigEntry - for config_entry in call.hass.config_entries.async_loaded_entries(DOMAIN): - coordinator = config_entry.runtime_data.coordinator - assert(coordinator) - if coordinator.source_entity_id and coordinator.source_entity_id == source_entity_id: - - coordinator.last_replaced =datetime_replaced - await coordinator.async_request_refresh() - - _LOGGER.debug( - "Entity %s battery replaced on %s", - source_entity_id, - str(datetime_replaced), - ) + # Check if entity_id exists in any sub config entry + entity_found = False - call.hass.bus.async_fire( - EVENT_BATTERY_REPLACED, - { - ATTR_DEVICE_ID: coordinator.device_id or "", - ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id - or "", - ATTR_DEVICE_NAME: coordinator.device_name, - ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, - ATTR_BATTERY_TYPE: coordinator.battery_type, - ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, - }, - ) - - _LOGGER.debug( - "Raised event battery replaced %s", - coordinator.device_id, - ) - - return None - - _LOGGER.error("Entity %s not configured in Battery Notes", source_entity_id) + for config_entry in call.hass.config_entries.async_loaded_entries(DOMAIN): + battery_notes_config_entry = cast(BatteryNotesConfigEntry, config_entry) + if not battery_notes_config_entry.runtime_data.subentry_coordinators: + continue + + for ( + coordinator + ) in battery_notes_config_entry.runtime_data.subentry_coordinators.values(): + if ( + coordinator.source_entity_id + and coordinator.source_entity_id == source_entity_id + ): + entity_found = True + coordinator.last_replaced = datetime_replaced + await coordinator.async_request_refresh() + + _LOGGER.debug( + "Entity %s battery replaced on %s", + source_entity_id, + str(datetime_replaced), + ) + + call.hass.bus.async_fire( + EVENT_BATTERY_REPLACED, + { + ATTR_DEVICE_ID: coordinator.device_id or "", + ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id or "", + ATTR_DEVICE_NAME: coordinator.device_name, + ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, + ATTR_BATTERY_TYPE: coordinator.battery_type, + ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, + }, + ) + + _LOGGER.debug( + "Raised event battery replaced %s", + coordinator.device_id, + ) + + return None + + if not entity_found: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="not_configured_in_battery_notes", + translation_placeholders={"source": source_entity_id}, + ) return None else: device_entry = device_registry.async_get(device_id) if not device_entry: - _LOGGER.error( - "Device %s not found", - device_id, + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="not_configured_in_battery_notes", + translation_placeholders={"source": device_id}, ) - return None + # Check if device_id exists in any sub config entry + device_found = False for config_entry in call.hass.config_entries.async_loaded_entries(DOMAIN): - coordinator = config_entry.runtime_data.coordinator - assert(coordinator) - if coordinator.device_id == device_id: - coordinator.last_replaced = datetime_replaced - await coordinator.async_request_refresh() - - _LOGGER.debug( - "Device %s battery replaced on %s", - device_id, - str(datetime_replaced), + battery_notes_config_entry = cast(BatteryNotesConfigEntry, config_entry) + if not battery_notes_config_entry.runtime_data.subentry_coordinators: + continue + + for ( + coordinator + ) in battery_notes_config_entry.runtime_data.subentry_coordinators.values(): + if coordinator.device_id == device_id: + device_found = True + coordinator.last_replaced = datetime_replaced + await coordinator.async_request_refresh() + + _LOGGER.debug( + "Device %s battery replaced on %s", + device_id, + str(datetime_replaced), + ) + + call.hass.bus.async_fire( + EVENT_BATTERY_REPLACED, + { + ATTR_DEVICE_ID: coordinator.device_id or "", + ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id or "", + ATTR_DEVICE_NAME: coordinator.device_name, + ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, + ATTR_BATTERY_TYPE: coordinator.battery_type, + ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, + }, + ) + + _LOGGER.debug( + "Raised event battery replaced %s", + coordinator.device_id, + ) + + # Found and dealt with, exit + return None + + if not device_found: + raise HomeAssistantError( + translation_domain=DOMAIN, + translation_key="not_configured_in_battery_notes", + translation_placeholders={"source": device_id}, ) - - call.hass.bus.async_fire( - EVENT_BATTERY_REPLACED, - { - ATTR_DEVICE_ID: coordinator.device_id or "", - ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id - or "", - ATTR_DEVICE_NAME: coordinator.device_name, - ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, - ATTR_BATTERY_TYPE: coordinator.battery_type, - ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, - }, - ) - - _LOGGER.debug( - "Raised event battery replaced %s", - coordinator.device_id, - ) - - # Found and dealt with, exit - return None - - _LOGGER.error( - "Device %s not configured in Battery Notes", - device_id, - ) return None @@ -190,72 +211,77 @@ async def _async_battery_last_reported(call: ServiceCall) -> ServiceResponse: """Handle the service call.""" days_last_reported = cast(int, call.data.get(SERVICE_DATA_DAYS_LAST_REPORTED)) - config_entry: BatteryNotesConfigEntry for config_entry in call.hass.config_entries.async_loaded_entries(DOMAIN): - coordinator = config_entry.runtime_data.coordinator - assert(coordinator) + battery_notes_config_entry = cast(BatteryNotesConfigEntry, config_entry) + if not battery_notes_config_entry.runtime_data.subentry_coordinators: + continue + + for ( + coordinator + ) in battery_notes_config_entry.runtime_data.subentry_coordinators.values(): + if coordinator.wrapped_battery and coordinator.last_reported: + time_since_lastreported = ( + datetime.fromisoformat(str(utcnow_no_timezone()) + "+00:00") + - coordinator.last_reported + ) + + if time_since_lastreported.days > days_last_reported: + call.hass.bus.async_fire( + EVENT_BATTERY_NOT_REPORTED, + { + ATTR_DEVICE_ID: coordinator.device_id or "", + ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id or "", + ATTR_DEVICE_NAME: coordinator.device_name, + ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, + ATTR_BATTERY_TYPE: coordinator.battery_type, + ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, + ATTR_BATTERY_LAST_REPORTED: coordinator.last_reported, + ATTR_BATTERY_LAST_REPORTED_DAYS: time_since_lastreported.days, + ATTR_BATTERY_LAST_REPORTED_LEVEL: coordinator.last_reported_level, + ATTR_BATTERY_LAST_REPLACED: coordinator.last_replaced, + }, + ) + + _LOGGER.debug( + "Raised event device %s not reported since %s", + coordinator.device_id, + str(coordinator.last_reported), + ) + return None - if coordinator.wrapped_battery and coordinator.last_reported: - time_since_lastreported = ( - datetime.fromisoformat(str(utcnow_no_timezone()) + "+00:00") - - coordinator.last_reported - ) - if time_since_lastreported.days > days_last_reported: +async def _async_battery_low(call: ServiceCall) -> ServiceResponse: + """Handle the service call.""" + + for config_entry in call.hass.config_entries.async_loaded_entries(DOMAIN): + battery_notes_config_entry = cast(BatteryNotesConfigEntry, config_entry) + if not battery_notes_config_entry.runtime_data.subentry_coordinators: + continue + + for ( + coordinator + ) in battery_notes_config_entry.runtime_data.subentry_coordinators.values(): + if coordinator.battery_low is True: call.hass.bus.async_fire( - EVENT_BATTERY_NOT_REPORTED, + EVENT_BATTERY_THRESHOLD, { ATTR_DEVICE_ID: coordinator.device_id or "", - ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id - or "", ATTR_DEVICE_NAME: coordinator.device_name, + ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id or "", + ATTR_BATTERY_LOW: coordinator.battery_low, + ATTR_BATTERY_LOW_THRESHOLD: coordinator.battery_low_threshold, ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, ATTR_BATTERY_TYPE: coordinator.battery_type, ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, - ATTR_BATTERY_LAST_REPORTED: coordinator.last_reported, - ATTR_BATTERY_LAST_REPORTED_DAYS: time_since_lastreported.days, - ATTR_BATTERY_LAST_REPORTED_LEVEL: coordinator.last_reported_level, + ATTR_BATTERY_LEVEL: coordinator.rounded_battery_level, + ATTR_PREVIOUS_BATTERY_LEVEL: coordinator.rounded_previous_battery_level, ATTR_BATTERY_LAST_REPLACED: coordinator.last_replaced, + ATTR_BATTERY_THRESHOLD_REMINDER: True, }, ) _LOGGER.debug( - "Raised event device %s not reported since %s", + "Raised event device %s battery low", coordinator.device_id, - str(coordinator.last_reported), ) return None - -async def _async_battery_low(call: ServiceCall) -> ServiceResponse: - """Handle the service call.""" - - config_entry: BatteryNotesConfigEntry - for config_entry in call.hass.config_entries.async_loaded_entries(DOMAIN): - coordinator = config_entry.runtime_data.coordinator - assert(coordinator) - - if coordinator.battery_low is True: - call.hass.bus.async_fire( - EVENT_BATTERY_THRESHOLD, - { - ATTR_DEVICE_ID: coordinator.device_id or "", - ATTR_DEVICE_NAME: coordinator.device_name, - ATTR_SOURCE_ENTITY_ID: coordinator.source_entity_id - or "", - ATTR_BATTERY_LOW: coordinator.battery_low, - ATTR_BATTERY_LOW_THRESHOLD: coordinator.battery_low_threshold, - ATTR_BATTERY_TYPE_AND_QUANTITY: coordinator.battery_type_and_quantity, - ATTR_BATTERY_TYPE: coordinator.battery_type, - ATTR_BATTERY_QUANTITY: coordinator.battery_quantity, - ATTR_BATTERY_LEVEL: coordinator.rounded_battery_level, - ATTR_PREVIOUS_BATTERY_LEVEL: coordinator.rounded_previous_battery_level, - ATTR_BATTERY_LAST_REPLACED: coordinator.last_replaced, - ATTR_BATTERY_THRESHOLD_REMINDER: True, - }, - ) - - _LOGGER.debug( - "Raised event device %s battery low", - coordinator.device_id, - ) - return None \ No newline at end of file diff --git a/custom_components/battery_notes/services.yaml b/custom_components/battery_notes/services.yaml index a8ea6e709..ae24c7973 100644 --- a/custom_components/battery_notes/services.yaml +++ b/custom_components/battery_notes/services.yaml @@ -8,8 +8,6 @@ set_battery_replaced: required: false selector: device: - filter: - - integration: battery_notes source_entity_id: name: Entity description: "Entity that has had its battery replaced (only used for entity associated battery notes)." diff --git a/custom_components/battery_notes/store.py b/custom_components/battery_notes/store.py index 6320ec57b..3826cf1c1 100644 --- a/custom_components/battery_notes/store.py +++ b/custom_components/battery_notes/store.py @@ -3,12 +3,13 @@ from __future__ import annotations import logging +from typing import Any, cast +from datetime import datetime from collections import OrderedDict from collections.abc import MutableMapping -from datetime import datetime -from typing import Any, cast import attr + from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.storage import Store @@ -27,6 +28,7 @@ @attr.s(slots=True, frozen=True) class DeviceEntry: + # pylint: disable=too-few-public-methods """Battery Notes Device storage Entry.""" device_id = attr.ib(type=str, default=None) @@ -37,6 +39,7 @@ class DeviceEntry: @attr.s(slots=True, frozen=True) class EntityEntry: + # pylint: disable=too-few-public-methods """Battery Notes Entity storage Entry.""" entity_id = attr.ib(type=str, default=None) @@ -49,7 +52,10 @@ class MigratableStore(Store): """Holds battery notes data.""" async def _async_migrate_func( - self, old_major_version: int, old_minor_version: int, data: dict + self, + old_major_version: int, # noqa: ARG002 + old_minor_version: int, # noqa: ARG002 + data: dict, ): # pylint: disable=arguments-renamed # pylint: disable=unused-argument @@ -121,7 +127,7 @@ async def async_delete(self): self.devices = {} @callback - def async_get_device(self, device_id)-> dict[str, Any] | None: + def async_get_device(self, device_id) -> dict[str, Any] | None: """Get an existing DeviceEntry by id.""" res = self.devices.get(device_id) return attr.asdict(res) if res else None diff --git a/custom_components/battery_notes/translations/ar.json b/custom_components/battery_notes/translations/ar.json index 7c78e7886..bc8106dc7 100644 --- a/custom_components/battery_notes/translations/ar.json +++ b/custom_components/battery_notes/translations/ar.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "إذا كنت بحاجة إلى مساعدة في التكوين، يمكنك الاطلاع على هذا الرابط: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "نوع الارتباط" - }, - "menu_options": { - "device": "الجهاز (مستحسن)", - "entity": "كيان" - }, - "title": "اختر نوع ارتباطك" - }, - "device": { - "data": { - "device_id": "جهاز", - "name": "اسم" - }, - "data_description": { - "name": "اتركه فارغًا سيأخذ الاسم من الجهاز المصدر." - } - }, - "entity": { - "data": { - "source_entity_id": "كيان", - "name": "اسم" - }, - "data_description": { - "name": "اتركه فارغًا سيأخذ الاسم من الكيان المصدر." - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "الصانع: {manufacturer}\nالطراز: {model}\nمعرف الطراز: {model_id}\nإصدار الجهاز: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "القالب لتحديد إذا كانت البطارية منخفضة، يجب أن يُرجع \"صحيح\" إذا كانت منخفضة.\nمطلوب فقط للمستويات غير القياسية للبطارية.", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "تم تحديد هذا الجهاز في المكتبة كجهاز يدوي، حيث تستخدم الإصدارات المختلفة أنواع بطاريات مختلفة ولا يمكن تعيينها في المكتبة.\nالخطوة التالية ستتيح لك تحديد نوع البطارية الخاص بك، ولكن يُرجى عدم تقديم طلب جهاز.", - "title": "التكوين اليدوي للجهاز\n\n\n\n" } }, "abort": { - "already_configured": "تم تكوين الجهاز بالفعل." + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "حدث خطأ غير معروف.", - "unconfigurable_entity": "لا يمكن إضافة هذا الكيان إلى ملاحظات البطارية." + "unknown": "حدث خطأ غير معروف." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "إذا كنت بحاجة إلى مساعدة في التكوين، يمكنك الاطلاع على هذا الرابط: {documentation_url}", + "data": { + "association_type": "نوع الارتباط" + }, + "menu_options": { + "device": "الجهاز (مستحسن)", + "entity": "كيان" + }, + "title": "اختر نوع ارتباطك" + }, + "device": { + "data": { + "device_id": "جهاز", + "name": "اسم" + }, + "data_description": { + "name": "اتركه فارغًا سيأخذ الاسم من الجهاز المصدر." + } + }, + "entity": { + "data": { + "source_entity_id": "كيان", + "name": "اسم" + }, + "data_description": { + "name": "اتركه فارغًا سيأخذ الاسم من الكيان المصدر." + } + }, + "battery": { + "description": "الصانع: {manufacturer}\nالطراز: {model}\nمعرف الطراز: {model_id}\nإصدار الجهاز: {hw_version}", + "data": { + "battery_type": "نوع البطارية", + "battery_quantity": "كَمّيَّة البطارية", + "battery_low_threshold": "عتبة البطارية المنخفضة", + "battery_low_template": "قالب البطارية المنخفضة", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "الصفر سيستخدم العتبة الافتراضية العالمية.", + "battery_low_template": "القالب لتحديد إذا كانت البطارية منخفضة، يجب أن يُرجع \"صحيح\" إذا كانت منخفضة.\nمطلوب فقط للمستويات غير القياسية للبطارية.", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "تم تحديد هذا الجهاز في المكتبة كجهاز يدوي، حيث تستخدم الإصدارات المختلفة أنواع بطاريات مختلفة ولا يمكن تعيينها في المكتبة.\nالخطوة التالية ستتيح لك تحديد نوع البطارية الخاص بك، ولكن يُرجى عدم تقديم طلب جهاز.", + "title": "التكوين اليدوي للجهاز\n\n\n\n" + }, + "reconfigure": { + "description": "الصانع: {manufacturer}\nالطراز: {model}\nمعرف الطراز: {model_id}\nإصدار الجهاز: {hw_version}", + "data": { + "name": "اسم", + "battery_type": "نوع البطارية", + "battery_quantity": "كَمّيَّة البطارية", + "battery_low_threshold": "عتبة البطارية المنخفضة", + "battery_low_template": "قالب البطارية المنخفضة", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "تركه فارغًا سيأخذ الاسم من الجهاز المصدر.", + "battery_low_threshold": "الصفر سيستخدم العتبة الافتراضية العالمية.", + "battery_low_template": "القالب لتحديد إذا كانت البطارية منخفضة، يجب أن يُرجع \"صحيح\" إذا كانت منخفضة.\nمطلوب فقط للمستويات غير القياسية للبطارية.", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "تم تكوين الجهاز بالفعل." + }, + "error": { + "unknown": "حدث خطأ غير معروف.", + "unconfigurable_entity": "لا يمكن إضافة هذا الكيان إلى ملاحظات البطارية.", + "orphaned_battery_note": "الجهاز أو الكيان المرتبط لم يعد موجودًا لهذه الملاحظة الخاصة بالبطارية." + } } }, "options": { "step": { "init": { - "description": "الصانع: {manufacturer}\nالطراز: {model}\nمعرف الطراز: {model_id}\nإصدار الجهاز: {hw_version}", "data": { - "name": "اسم", - "battery_type": "نوع البطارية", - "battery_quantity": "كَمَيَّة البطارية", - "battery_low_threshold": "عتبة البطارية المنخفضة", - "battery_low_template": "قالب البطارية المنخفضة", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "تركه فارغًا سيأخذ الاسم من الجهاز المصدر.", - "battery_low_threshold": "الصفر سيستخدم العتبة الافتراضية العالمية.", - "battery_low_template": "القالب لتحديد إذا كانت البطارية منخفضة، يجب أن يُرجع \"صحيح\" إذا كانت منخفضة.\nمطلوب فقط للمستويات غير القياسية للبطارية.", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "الجهاز أو الكيان المرتبط لم يعد موجودًا لهذه الملاحظة الخاصة بالبطارية.", "unknown": "حدث خطأ غير معروف." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/ca.json b/custom_components/battery_notes/translations/ca.json index f1a60dfe3..0deb8c569 100644 --- a/custom_components/battery_notes/translations/ca.json +++ b/custom_components/battery_notes/translations/ca.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Si necessiteu ajuda amb la configuració, mireu aquí: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Tipus d'associació" - }, - "menu_options": { - "device": "Dispositiu (recomanat)", - "entity": "Entitat" - }, - "title": "Tria el tipus d'associació" - }, - "device": { - "data": { - "device_id": "Dispositiu", - "name": "Nom" - }, - "data_description": { - "name": "Si ho deixes en blanc, agafarà el nom del dispositiu d'origen" - } - }, - "entity": { - "data": { - "source_entity_id": "Entitat", - "name": "Nom" - }, - "data_description": { - "name": "Si ho deixes en blanc, agafarà el nom de l'entitat d'origen" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Fabricant: {manufacturer}\nModel: {model}\nIdentificador del model: {model_id}\nVersió de maquinari: {hw_version}", @@ -45,41 +20,135 @@ "filter_outliers": "Filtra les grans caigudes del nivell de bateria, reduint els esdeveniments que es disparen falsament en els dispositius que informen erròniament dels nivells de manera ocasional." } }, - "manual": { - "description": "Aquest dispositiu està marcat a la biblioteca com a manual, les variants utilitzen diferents tipus de bateries, de manera que no es pot configurar a la biblioteca.\nEl següent pas us permetrà configurar el tipus de bateria, però no envieu cap sol·licitud de dispositiu.", - "title": "Configuració manual del dispositiu" + "abort": { + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" + }, + "error": { + "unknown": "S'ha produït un error desconegut" + } + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Si necessiteu ajuda amb la configuració, mireu aquí: {documentation_url}", + "data": { + "association_type": "Tipus d'associació" + }, + "menu_options": { + "device": "Dispositiu (recomanat)", + "entity": "Entitat" + }, + "title": "Tria el tipus d'associació" + }, + "device": { + "data": { + "device_id": "Dispositiu", + "name": "Nom" + }, + "data_description": { + "name": "Si ho deixes en blanc, agafarà el nom del dispositiu d'origen" + } + }, + "entity": { + "data": { + "source_entity_id": "Entitat", + "name": "Nom" + }, + "data_description": { + "name": "Si ho deixes en blanc, agafarà el nom de l'entitat d'origen" + } + }, + "battery": { + "description": "Fabricant: {manufacturer}\nModel: {model}\nIdentificador del model: {model_id}\nVersió de maquinari: {hw_version}", + "data": { + "battery_type": "Tipus de bateria", + "battery_quantity": "Quantitat de bateries", + "battery_low_threshold": "Llindar baix de la bateria", + "battery_low_template": "Plantilla de bateria baixa", + "filter_outliers": "iltrar valors atípics" + }, + "data_description": { + "battery_low_threshold": "0 utilitzarà el llindar predeterminat global", + "battery_low_template": "La plantilla per determinar que la bateria és baixa, hauria de tornar correcte si és baixa\nNomés és necessari per a nivells de bateria no estàndard", + "filter_outliers": "Filtra les grans caigudes del nivell de bateria, reduint els esdeveniments que es disparen falsament en els dispositius que informen erròniament dels nivells de manera ocasional." + } + }, + "manual": { + "description": "Aquest dispositiu està marcat a la biblioteca com a manual, les variants utilitzen diferents tipus de bateries, de manera que no es pot configurar a la biblioteca.\nEl següent pas us permetrà configurar el tipus de bateria, però no envieu cap sol·licitud de dispositiu.", + "title": "Configuració manual del dispositiu" + }, + "reconfigure": { + "description": "Fabricant: {manufacturer}\nModel: {model}\nIdentificador del model: {model_id}\nVersió de maquinari: {hw_version}", + "data": { + "name": "Nom", + "battery_type": "Tipus de bateria", + "battery_quantity": "Quantitat de bateries", + "battery_low_threshold": "Llindar baix de la bateria", + "battery_low_template": "Plantilla de bateria baixa", + "filter_outliers": "iltrar valors atípics" + }, + "data_description": { + "name": "Si ho deixes en blanc, agafarà el nom del dispositiu d'origen", + "battery_low_threshold": "0 utilitzarà el llindar predeterminat global", + "battery_low_template": "La plantilla per determinar que la bateria és baixa, hauria de tornar correcte si és baixa\nNomés és necessari per a nivells de bateria no estàndard", + "filter_outliers": "Filtra les grans caigudes del nivell de bateria, reduint els esdeveniments que es disparen falsament en els dispositius que informen erròniament dels nivells de manera ocasional." + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "El dispositiu ja està configurat" + }, + "error": { + "unknown": "S'ha produït un error desconegut", + "unconfigurable_entity": "No es pot afegir aquesta entitat al Battery Notes ", + "orphaned_battery_note": "El dispositiu o l'entitat associada ja no existeix per a aquesta Battery Note." } - }, - "abort": { - "already_configured": "El dispositiu ja està configurat" - }, - "error": { - "unknown": "S'ha produït un error desconegut", - "unconfigurable_entity": "No es pot afegir aquesta entitat al Battery Notes " } }, "options": { "step": { "init": { - "description": "Fabricant: {manufacturer}\nModel: {model}\nIdentificador del model: {model_id}\nVersió de maquinari: {hw_version}", "data": { - "name": "Nom", - "battery_type": "Tipus de bateria", - "battery_quantity": "Quantitat de bateries", - "battery_low_threshold": "Llindar baix de la bateria", - "battery_low_template": "Plantilla de bateria baixa", - "filter_outliers": "iltrar valors atípics" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Si ho deixes en blanc, agafarà el nom del dispositiu d'origen", - "battery_low_threshold": "0 utilitzarà el llindar predeterminat global", - "battery_low_template": "La plantilla per determinar que la bateria és baixa, hauria de tornar correcte si és baixa\nNomés és necessari per a nivells de bateria no estàndard", - "filter_outliers": "Filtra les grans caigudes del nivell de bateria, reduint els esdeveniments que es disparen falsament en els dispositius que informen erròniament dels nivells de manera ocasional." + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "El dispositiu o l'entitat associada ja no existeix per a aquesta Battery Note.", "unknown": "S'ha produït un error desconegut" } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/cs.json b/custom_components/battery_notes/translations/cs.json index dae37bc46..49bbd311f 100644 --- a/custom_components/battery_notes/translations/cs.json +++ b/custom_components/battery_notes/translations/cs.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Pokud potřebujete pomoc s konfigurací, podívejte se zde: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Typ přidružení" - }, - "menu_options": { - "device": "Zařízení (doporučeno)", - "entity": "Entita" - }, - "title": "Vyberte typ přidružení" - }, - "device": { - "data": { - "device_id": "Zařízení", - "name": "Název" - }, - "data_description": { - "name": "Ponecháte-li prázdné, převezme název ze zdrojového zařízení" - } - }, - "entity": { - "data": { - "source_entity_id": "Entita", - "name": "Název" - }, - "data_description": { - "name": "Ponecháte-li prázdné, převezme název ze zdrojové entity" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Výrobce: {manufacturer}\nModel: {model}\nID modelu: {model_id}\nHardware verze: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Šablona pro zjištění, zda je baterie vybitá, měla by vracet hodnotu PRAVDA (true), pokud je vybitá.\nPotřebné pouze pro nestandardní úrovně nabití baterie", "filter_outliers": "Filtrovat velké ztráty úrovně baterie, což snižuje falešné spouštění událostí na zařízení, která občas chybně hlásí úrovně" } - }, - "manual": { - "description": "Toto zařízení je v knihovně označeno jako manuální, varianty používají různé typy baterie, takže nemůže být nastaveno v knihovně.\nDalší krok vám umožní nastavit typ baterie, ale prosím nepotvrzujte žádost zařízení.", - "title": "Ruční nastavení zařízení" } }, "abort": { - "already_configured": "Zařízení je již nastaveno" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Vyskytla se neznámá chyba.", - "unconfigurable_entity": "Tuto entitu nelze přidat do Battery Notes." + "unknown": "Vyskytla se neznámá chyba." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Pokud potřebujete pomoc s konfigurací, podívejte se zde: {documentation_url}", + "data": { + "association_type": "Typ přidružení" + }, + "menu_options": { + "device": "Zařízení (doporučeno)", + "entity": "Entita" + }, + "title": "Vyberte typ přidružení" + }, + "device": { + "data": { + "device_id": "Zařízení", + "name": "Název" + }, + "data_description": { + "name": "Ponecháte-li prázdné, převezme název ze zdrojového zařízení" + } + }, + "entity": { + "data": { + "source_entity_id": "Entita", + "name": "Název" + }, + "data_description": { + "name": "Ponecháte-li prázdné, převezme název ze zdrojové entity" + } + }, + "battery": { + "description": "Výrobce: {manufacturer}\nModel: {model}\nID modelu: {model_id}\nHardware verze: {hw_version}", + "data": { + "battery_type": "Typ baterie", + "battery_quantity": "Množství baterií", + "battery_low_threshold": "Práh nízkého stavu baterie", + "battery_low_template": "Šablona nízkého stavu baterie", + "filter_outliers": "Filtrovat odlehlé hodnoty" + }, + "data_description": { + "battery_low_threshold": "0 se použije jako globální výchozí práh", + "battery_low_template": "Šablona pro zjištění, zda je baterie vybitá, měla by vracet hodnotu PRAVDA (true), pokud je vybitá.\nPotřebné pouze pro nestandardní úrovně nabití baterie", + "filter_outliers": "Filtrovat velké ztráty úrovně baterie, což snižuje falešné spouštění událostí na zařízení, která občas chybně hlásí úrovně" + } + }, + "manual": { + "description": "Toto zařízení je v knihovně označeno jako manuální, varianty používají různé typy baterie, takže nemůže být nastaveno v knihovně.\nDalší krok vám umožní nastavit typ baterie, ale prosím nepotvrzujte žádost zařízení.", + "title": "Ruční nastavení zařízení" + }, + "reconfigure": { + "description": "Výrobce: {manufacturer}\nModel: {model}\nID modelu: {model_id}\nHardware verze: {hw_version}", + "data": { + "name": "Název", + "battery_type": "Typ baterie", + "battery_quantity": "Množství baterií", + "battery_low_threshold": "Práh nízkého stavu baterie", + "battery_low_template": "Šablona nízkého stavu baterie", + "filter_outliers": "Filtrovat odlehlé hodnoty" + }, + "data_description": { + "name": "Ponecháte-li prázdné, převezme název ze zdrojového zařízení", + "battery_low_threshold": "0 se použije jako globální výchozí práh", + "battery_low_template": "Šablona k určení zda je stav baterie nízký, měla by vrátit hodnotu pravda, pokud je nízký\nJe potřeba pouze pro nestandardní úroveně baterie", + "filter_outliers": "Filtrovat velké ztráty úrovně baterie, což snižuje falešné spouštění událostí na zařízení, která občas chybně hlásí úrovně" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Zařízení je již nastaveno" + }, + "error": { + "unknown": "Vyskytla se neznámá chyba.", + "unconfigurable_entity": "Tuto entitu nelze přidat do Battery Notes.", + "orphaned_battery_note": "Přidružené zařízení nebo entita pro tuto poznámku k baterii již neexistuje." + } } }, "options": { "step": { "init": { - "description": "Výrobce: {manufacturer}\nModel: {model}\nID modelu: {model_id}\nHardware verze: {hw_version}", "data": { - "name": "Název", - "battery_type": "Typ baterie", - "battery_quantity": "Množství baterií", - "battery_low_threshold": "Práh nízkého stavu baterie", - "battery_low_template": "Šablona nízkého stavu baterie", - "filter_outliers": "Filtrovat odlehlé hodnoty" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Ponecháte-li prázdné, převezme název ze zdrojového zařízení", - "battery_low_threshold": "0 se použije jako globální výchozí práh", - "battery_low_template": "Šablona k určení zda je stav baterie nízký, měla by vrátit hodnotu pravda, pokud je nízký\nJe potřeba pouze pro nestandardní úroveně baterie", - "filter_outliers": "Filtrovat velké ztráty úrovně baterie, což snižuje falešné spouštění událostí na zařízení, která občas chybně hlásí úrovně" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Přidružené zařízení nebo entita pro tuto poznámku k baterii již neexistuje.", "unknown": "Vyskytla se neznámá chyba." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/da.json b/custom_components/battery_notes/translations/da.json index 0757d16b6..7a875e314 100644 --- a/custom_components/battery_notes/translations/da.json +++ b/custom_components/battery_notes/translations/da.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Hvis du har brug for hjælp til konfigurationen, så kig her: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Association type" - }, - "menu_options": { - "device": "Enhed (anbefales)", - "entity": "Enhed" - }, - "title": "Choose your association type" - }, - "device": { - "data": { - "device_id": "Enhed", - "name": "Navn" - }, - "data_description": { - "name": "Hvis du lader dette felt være tomt, hentes navnet fra kildeenheden" - } - }, - "entity": { - "data": { - "source_entity_id": "Enhed", - "name": "Navn" - }, - "data_description": { - "name": "Hvis du lader stå tomt, tages navnet fra kildeenheden" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Fabrikant: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Skabelon til at bestemme om et batteri er lavt, skal returnere sand, hvis lavt.\nKun nødvendigt for ikke-standard batteriniveauer", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", - "title": "Manuel konfiguration enheden" } }, "abort": { - "already_configured": "Enheden er allerede konfigureret" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Der opstod en ukendt fejl.", - "unconfigurable_entity": "Det er ikke muligt at tilføje denne enhed til Battery Notes." + "unknown": "Der opstod en ukendt fejl." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Hvis du har brug for hjælp til konfigurationen, så kig her: {documentation_url}", + "data": { + "association_type": "Association type" + }, + "menu_options": { + "device": "Enhed (anbefales)", + "entity": "Enhed" + }, + "title": "Choose your association type" + }, + "device": { + "data": { + "device_id": "Enhed", + "name": "Navn" + }, + "data_description": { + "name": "Hvis du lader dette felt være tomt, hentes navnet fra kildeenheden" + } + }, + "entity": { + "data": { + "source_entity_id": "Enhed", + "name": "Navn" + }, + "data_description": { + "name": "Hvis du lader stå tomt, tages navnet fra kildeenheden" + } + }, + "battery": { + "description": "Fabrikant: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "battery_type": "Batteri type", + "battery_quantity": "Antal batterier", + "battery_low_threshold": "Lavt batteri niveau", + "battery_low_template": "Batteri lav skabelon", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 vil bruge den globale standardtærskel", + "battery_low_template": "Skabelon til at bestemme om et batteri er lavt, skal returnere sand, hvis lavt.\nKun nødvendigt for ikke-standard batteriniveauer", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", + "title": "Manuel konfiguration enheden" + }, + "reconfigure": { + "description": "Fabrikant: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "name": "Navn", + "battery_type": "Batteri type", + "battery_quantity": "Antal batterier", + "battery_low_threshold": "Lavt batteri niveau", + "battery_low_template": "Batteri lav skabelon", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Hvis du lader dette felt være tomt, hentes navnet fra kildeenheden", + "battery_low_threshold": "0 vil bruge den globale standardtærskel", + "battery_low_template": "Skabelon til at bestemme om et batteri er lavt, skal returnere sand, hvis lavt\nKun nødvendigt for ikke-standard batteriniveauer", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Enheden er allerede konfigureret" + }, + "error": { + "unknown": "Der opstod en ukendt fejl.", + "unconfigurable_entity": "Det er ikke muligt at tilføje denne enhed til Battery Notes.", + "orphaned_battery_note": "Den tilknyttede enhed eller enhed eksisterer ikke længere for dette Battery Note." + } } }, "options": { "step": { "init": { - "description": "Fabrikant: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", "data": { - "name": "Navn", - "battery_type": "Batteri type", - "battery_quantity": "Antal batterier", - "battery_low_threshold": "Lavt batteri niveau", - "battery_low_template": "Batteri lav skabelon", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Hvis du lader dette felt være tomt, hentes navnet fra kildeenheden", - "battery_low_threshold": "0 vil bruge den globale standardtærskel", - "battery_low_template": "Skabelon til at bestemme om et batteri er lavt, skal returnere sand, hvis lavt\nKun nødvendigt for ikke-standard batteriniveauer", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Den tilknyttede enhed eller enhed eksisterer ikke længere for dette Battery Note.", "unknown": "Der opstod en ukendt fejl." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/de.json b/custom_components/battery_notes/translations/de.json index 7e59c180d..181402bbe 100644 --- a/custom_components/battery_notes/translations/de.json +++ b/custom_components/battery_notes/translations/de.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Hilfe zur Konfiguration findest du unter: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Verbindungstyp" - }, - "menu_options": { - "device": "Gerät (empfohlen)", - "entity": "Entität" - }, - "title": "Wählen Sie den Verbindungstyp" - }, - "device": { - "data": { - "device_id": "Gerät", - "name": "Name" - }, - "data_description": { - "name": "Wenn du nichts eingibst, wird der Name vom Quellgerät übernommen" - } - }, - "entity": { - "data": { - "source_entity_id": "Entität", - "name": "Name" - }, - "data_description": { - "name": "Leer lassen wird den Namen von der Quell-Entität übernehmen" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Hersteller: {manufacturer}\nModell: {model}\nModell ID: {model_id}\nHardware Version: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Vorlage um zu bestimmen, ob eine Batterie schwach ist; sollte \"wahr\" (true) rückmelden, wenn schwach.\nNur für nicht-Standard Batteriewerte benötigt.", "filter_outliers": "Filtere größere Sprünge des Ladestandes der Batterie bei Geräten, die ihren Ladestand nur sporadisch mitteilen, um fehlerhafte Auslösungen zu verhindern" } - }, - "manual": { - "description": "Dieses Gerät ist in der Bibliothek als \"manuell einzurichten\" gekennzeichnet, verschiedene Varianten verwenden unterschiedliche Batterietypen, so dass es in der Bibliothek nicht eingestellt werden kann.\nIm nächsten Schritt können Sie Ihren Batterietyp einstellen, aber bitte keine Geräteanforderung einreichen.", - "title": "Gerät manuell konfigurieren" } }, "abort": { - "already_configured": "Das Gerät ist bereits konfiguriert." + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Ein unbekannter Fehler ist aufgetreten.", - "unconfigurable_entity": "Es ist nicht möglich, diese Entität zu einem Batteriestatus hinzuzufügen." + "unknown": "Ein unbekannter Fehler ist aufgetreten." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Hilfe zur Konfiguration findest du unter: {documentation_url}", + "data": { + "association_type": "Verbindungstyp" + }, + "menu_options": { + "device": "Gerät (empfohlen)", + "entity": "Entität" + }, + "title": "Wählen Sie den Verbindungstyp" + }, + "device": { + "data": { + "device_id": "Gerät", + "name": "Name" + }, + "data_description": { + "name": "Wenn du nichts eingibst, wird der Name vom Quellgerät übernommen" + } + }, + "entity": { + "data": { + "source_entity_id": "Entität", + "name": "Name" + }, + "data_description": { + "name": "Leer lassen wird den Namen von der Quell-Entität übernehmen" + } + }, + "battery": { + "description": "Hersteller: {manufacturer}\nModell: {model}\nModell ID: {model_id}\nHardware Version: {hw_version}", + "data": { + "battery_type": "Batterieart", + "battery_quantity": "Batteriemenge", + "battery_low_threshold": "Schwelle für niedrigen Batteriestand", + "battery_low_template": "Vorlage für niedrigen Batteriestand", + "filter_outliers": "Ausreißer filtern" + }, + "data_description": { + "battery_low_threshold": "0 verwendet den globalen Standardschwellenwert", + "battery_low_template": "Vorlage um zu bestimmen, ob eine Batterie schwach ist; sollte \"wahr\" (true) rückmelden, wenn schwach.\nNur für nicht-Standard Batteriewerte benötigt.", + "filter_outliers": "Filtere größere Sprünge des Ladestandes der Batterie bei Geräten, die ihren Ladestand nur sporadisch mitteilen, um fehlerhafte Auslösungen zu verhindern" + } + }, + "manual": { + "description": "Dieses Gerät ist in der Bibliothek als \"manuell einzurichten\" gekennzeichnet, verschiedene Varianten verwenden unterschiedliche Batterietypen, so dass es in der Bibliothek nicht eingestellt werden kann.\nIm nächsten Schritt können Sie Ihren Batterietyp einstellen, aber bitte keine Geräteanforderung einreichen.", + "title": "Gerät manuell konfigurieren" + }, + "reconfigure": { + "description": "Hersteller: {manufacturer}\nModell: {model}\nModell ID: {model_id}\nHardware Version: {hw_version}", + "data": { + "name": "Name", + "battery_type": "Batterieart", + "battery_quantity": "Batteriemenge", + "battery_low_threshold": "Schwelle für niedrigen Batteriestand", + "battery_low_template": "Vorlage für niedrigen Batteriestand", + "filter_outliers": "Ausreißer filtern" + }, + "data_description": { + "name": "Wenn du nichts eingibst, wird der Name vom Quellgerät übernommen", + "battery_low_threshold": "0 verwendet den globalen Standardschwellenwert", + "battery_low_template": "Vorlage um zu bestimmen, ob eine Batterie schwach ist; sollte \"wahr\" (true) rückmelden, wenn schwach.\nNur für nicht-Standard Batteriewerte benötigt.", + "filter_outliers": "Filtere größere Sprünge des Ladestandes der Batterie bei Geräten, die ihren Ladestand nur sporadisch mitteilen, um fehlerhafte Auslösungen zu verhindern" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Das Gerät ist bereits konfiguriert." + }, + "error": { + "unknown": "Ein unbekannter Fehler ist aufgetreten.", + "unconfigurable_entity": "Es ist nicht möglich, diese Entität zu einem Batteriestatus hinzuzufügen.", + "orphaned_battery_note": "Das zugeordnete Gerät für diesen Eintrag in \"Battery Notes\" ist nicht mehr verfügbar" + } } }, "options": { "step": { "init": { - "description": "Hersteller: {manufacturer}\nModell: {model}\nModell ID: {model_id}\nHardware Version: {hw_version}", "data": { - "name": "Name", - "battery_type": "Batterieart", - "battery_quantity": "Batteriemenge", - "battery_low_threshold": "Schwelle für niedrigen Batteriestand", - "battery_low_template": "Vorlage für niedrigen Batteriestand", - "filter_outliers": "Ausreißer filtern" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Wenn du nichts eingibst, wird der Name vom Quellgerät übernommen", - "battery_low_threshold": "0 verwendet den globalen Standardschwellenwert", - "battery_low_template": "Vorlage um zu bestimmen, ob eine Batterie schwach ist; sollte \"wahr\" (true) rückmelden, wenn schwach.\nNur für nicht-Standard Batteriewerte benötigt.", - "filter_outliers": "Filtere größere Sprünge des Ladestandes der Batterie bei Geräten, die ihren Ladestand nur sporadisch mitteilen, um fehlerhafte Auslösungen zu verhindern" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Das zugeordnete Gerät für diesen Eintrag in \"Battery Notes\" ist nicht mehr verfügbar", "unknown": "Ein unbekannter Fehler ist aufgetreten." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/el.json b/custom_components/battery_notes/translations/el.json index 6c6248988..7a0924f4c 100644 --- a/custom_components/battery_notes/translations/el.json +++ b/custom_components/battery_notes/translations/el.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Αν χρειάζεστε βοήθεια με τις ρυθμίσεις παραμέτρων ρίξτε μια ματιά εδώ: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Τύπος συσχετισμού" - }, - "menu_options": { - "device": "Συσκευή (συνιστάται)", - "entity": "Οντότητα" - }, - "title": "Επιλέξτε τον τύπο συσχέτισης" - }, - "device": { - "data": { - "device_id": "Συσκευή", - "name": "Ονομα" - }, - "data_description": { - "name": "Αφήνοντάς το κενό θα πάρει το όνομα από τη συσκευή προέλευσης" - } - }, - "entity": { - "data": { - "source_entity_id": "Οντότητα", - "name": "Ονομα" - }, - "data_description": { - "name": "Αφήνοντάς το κενό θα πάρει το όνομα από την οντότητα προέλευσης" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Κατασκευαστής: {manufacturer}\nΜοντέλο: {model}\nID Μοντέλου: {model_id}\nΕκδοση υλικού: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Template για τον προσδιορισμό μιας μπαταρίας είναι χαμηλή, θα πρέπει να επιστρέψει true εάν είναι χαμηλή\nΧρειάζεται μόνο για μη τυπικές στάθμες μπαταρίας", "filter_outliers": "Φιλτράρετε μεγάλες πτώσεις στάθμης μπαταρίας, περιορίζοντας την πυροδότηση ψευδών συμβάντων σε συσκευές που εσφαλμένα αναφέρουν επίπεδα περιστασιακά" } - }, - "manual": { - "description": "Αυτή η συσκευή επισημαίνεται στη βιβλιοθήκη ως χειροκίνητη, οι παραλλαγές χρησιμοποιούν διαφορετικούς τύπους μπαταρίας ώστε να μην μπορεί να οριστεί στη βιβλιοθήκη.\nΤο επόμενο βήμα θα σας επιτρέψει να ορίσετε τον τύπο μπαταρίας σας αλλά μην υποβάλετε αίτημα συσκευής.", - "title": "Χειροκίνητη διαμόρφωση συσκευής" } }, "abort": { - "already_configured": "Η συσκευή έχει ήδη ρυθμιστεί" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Προέκυψε άγνωστο σφάλμα.", - "unconfigurable_entity": "Δεν είναι δυνατή η προσθήκη αυτής της οντότητας στο Battery Notes." + "unknown": "Προέκυψε άγνωστο σφάλμα." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Αν χρειάζεστε βοήθεια με τις ρυθμίσεις παραμέτρων ρίξτε μια ματιά εδώ: {documentation_url}", + "data": { + "association_type": "Τύπος συσχετισμού" + }, + "menu_options": { + "device": "Συσκευή (συνιστάται)", + "entity": "Οντότητα" + }, + "title": "Επιλέξτε τον τύπο συσχέτισης" + }, + "device": { + "data": { + "device_id": "Συσκευή", + "name": "Ονομα" + }, + "data_description": { + "name": "Αφήνοντάς το κενό θα πάρει το όνομα από τη συσκευή προέλευσης" + } + }, + "entity": { + "data": { + "source_entity_id": "Οντότητα", + "name": "Ονομα" + }, + "data_description": { + "name": "Αφήνοντάς το κενό θα πάρει το όνομα από την οντότητα προέλευσης" + } + }, + "battery": { + "description": "Κατασκευαστής: {manufacturer}\nΜοντέλο: {model}\nID Μοντέλου: {model_id}\nΕκδοση υλικού: {hw_version}", + "data": { + "battery_type": "Τύπος μπαταρίας", + "battery_quantity": "Αριθμός μπαταριών", + "battery_low_threshold": "Ελάχιστο όριο μπαταρίας", + "battery_low_template": "Template χαμηλής στάθμης μπαταρίας", + "filter_outliers": "Φιλτράρισμα ακραίων τιμών" + }, + "data_description": { + "battery_low_threshold": "0 θα χρησιμοποιηθεί το καθολικό προεπιλεγμένο ελάχιστο όριο", + "battery_low_template": "Template για τον προσδιορισμό μιας μπαταρίας είναι χαμηλή, θα πρέπει να επιστρέψει true εάν είναι χαμηλή\nΧρειάζεται μόνο για μη τυπικές στάθμες μπαταρίας", + "filter_outliers": "Φιλτράρετε μεγάλες πτώσεις στάθμης μπαταρίας, περιορίζοντας την πυροδότηση ψευδών συμβάντων σε συσκευές που εσφαλμένα αναφέρουν επίπεδα περιστασιακά" + } + }, + "manual": { + "description": "Αυτή η συσκευή επισημαίνεται στη βιβλιοθήκη ως χειροκίνητη, οι παραλλαγές χρησιμοποιούν διαφορετικούς τύπους μπαταρίας ώστε να μην μπορεί να οριστεί στη βιβλιοθήκη.\nΤο επόμενο βήμα θα σας επιτρέψει να ορίσετε τον τύπο μπαταρίας σας αλλά μην υποβάλετε αίτημα συσκευής.", + "title": "Χειροκίνητη διαμόρφωση συσκευής" + }, + "reconfigure": { + "description": "Κατασκευαστής: {manufacturer}\nΜοντέλο: {model}\nID Μοντέλου: {model_id}\nΕκδοση υλικού: {hw_version}", + "data": { + "name": "Ονομα", + "battery_type": "Τύπος μπαταρίας", + "battery_quantity": "Αριθμός μπαταριών", + "battery_low_threshold": "Ελάχιστο όριο μπαταρίας", + "battery_low_template": "Template χαμηλής στάθμης μπαταρίας", + "filter_outliers": "Φιλτράρισμα ακραίων τιμών" + }, + "data_description": { + "name": "Αφήνοντάς το κενό θα πάρει το όνομα από τη συσκευή προέλευσης", + "battery_low_threshold": "0 θα χρησιμοποιηθεί το καθολικό προεπιλεγμένο ελάχιστο όριο", + "battery_low_template": "Template για τον προσδιορισμό μιας μπαταρίας είναι χαμηλή, θα πρέπει να επιστρέψει true εάν είναι χαμηλή\nΧρειάζεται μόνο για μη τυπικές στάθμες μπαταρίας", + "filter_outliers": "Φιλτράρετε μεγάλες πτώσεις στάθμης μπαταρίας, περιορίζοντας την πυροδότηση ψευδών συμβάντων σε συσκευές που εσφαλμένα αναφέρουν επίπεδα περιστασιακά" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Η συσκευή έχει ήδη ρυθμιστεί" + }, + "error": { + "unknown": "Προέκυψε άγνωστο σφάλμα.", + "unconfigurable_entity": "Δεν είναι δυνατή η προσθήκη αυτής της οντότητας στο Battery Notes.", + "orphaned_battery_note": "Η συσχετιζόμενη συσκευή ή οντότητα δεν υπάρχει πλέον για αυτήν την Σημείωση μπαταρίας." + } } }, "options": { "step": { "init": { - "description": "Κατασκευαστής: {manufacturer}\nΜοντέλο: {model}\nID Μοντέλου: {model_id}\nΕκδοση υλικού: {hw_version}", "data": { - "name": "Ονομα", - "battery_type": "Τύπος μπαταρίας", - "battery_quantity": "Αριθμός μπαταριών", - "battery_low_threshold": "Ελάχιστο όριο μπαταρίας", - "battery_low_template": "Template χαμηλής στάθμης μπαταρίας", - "filter_outliers": "Φιλτράρισμα ακραίων τιμών" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Αφήνοντάς το κενό θα πάρει το όνομα από τη συσκευή προέλευσης", - "battery_low_threshold": "0 θα χρησιμοποιηθεί το καθολικό προεπιλεγμένο ελάχιστο όριο", - "battery_low_template": "Template για τον προσδιορισμό μιας μπαταρίας είναι χαμηλή, θα πρέπει να επιστρέψει true εάν είναι χαμηλή\nΧρειάζεται μόνο για μη τυπικές στάθμες μπαταρίας", - "filter_outliers": "Φιλτράρετε μεγάλες πτώσεις στάθμης μπαταρίας, περιορίζοντας την πυροδότηση ψευδών συμβάντων σε συσκευές που εσφαλμένα αναφέρουν επίπεδα περιστασιακά" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Η συσχετιζόμενη συσκευή ή οντότητα δεν υπάρχει πλέον για αυτήν την Σημείωση μπαταρίας.", "unknown": "Προέκυψε άγνωστο σφάλμα." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/en.json b/custom_components/battery_notes/translations/en.json index 3ab0abd97..2a92399f6 100644 --- a/custom_components/battery_notes/translations/en.json +++ b/custom_components/battery_notes/translations/en.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "If you need help with the configuration have a look here: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Association type" - }, - "menu_options": { - "device": "Device (recommended)", - "entity": "Entity" - }, - "title": "Choose your association type" - }, - "device": { - "data": { - "device_id": "Device", - "name": "Name" - }, - "data_description": { - "name": "Leaving blank will take the name from the source device" - } - }, - "entity": { - "data": { - "source_entity_id": "Entity", - "name": "Name" - }, - "data_description": { - "name": "Leaving blank will take the name from the source entity" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", - "title": "Device manual configuration" } }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Unknown error occurred.", - "unconfigurable_entity": "It is not possible to add this entity to Battery Notes." + "unknown": "Unknown error occurred." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "If you need help with the configuration have a look here: {documentation_url}", + "data": { + "association_type": "Association type" + }, + "menu_options": { + "device": "Device (recommended)", + "entity": "Entity" + }, + "title": "Choose your association type" + }, + "device": { + "data": { + "device_id": "Device", + "name": "Name" + }, + "data_description": { + "name": "Leaving blank will take the name from the source device" + } + }, + "entity": { + "data": { + "source_entity_id": "Entity", + "name": "Name" + }, + "data_description": { + "name": "Leaving blank will take the name from the source entity" + } + }, + "battery": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "battery_type": "Battery type", + "battery_quantity": "Battery quantity", + "battery_low_threshold": "Battery low threshold", + "battery_low_template": "Battery low template", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 will use the global default threshold", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", + "title": "Device manual configuration" + }, + "reconfigure": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "name": "Name", + "battery_type": "Battery type", + "battery_quantity": "Battery quantity", + "battery_low_threshold": "Battery low threshold", + "battery_low_template": "Battery low template", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Leaving blank will take the name from the source device", + "battery_low_threshold": "0 will use the global default threshold", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Device is already configured" + }, + "error": { + "unknown": "Unknown error occurred.", + "unconfigurable_entity": "It is not possible to add this entity to Battery Notes.", + "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note." + } } }, "options": { "step": { "init": { - "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", "data": { - "name": "Name", - "battery_type": "Battery type", - "battery_quantity": "Battery quantity", - "battery_low_threshold": "Battery low threshold", - "battery_low_template": "Battery low template", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Leaving blank will take the name from the source device", - "battery_low_threshold": "0 will use the global default threshold", - "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.", "unknown": "Unknown error occurred." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/es-ES.json b/custom_components/battery_notes/translations/es-ES.json index 4db4d05a3..db058f735 100644 --- a/custom_components/battery_notes/translations/es-ES.json +++ b/custom_components/battery_notes/translations/es-ES.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Si necesitas ayuda con la configuración, echa un vistazo aquí: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Tipo de asociación" - }, - "menu_options": { - "device": "Dispositivo (recomendado)", - "entity": "Entidad" - }, - "title": "Elija su tipo de asociación" - }, - "device": { - "data": { - "device_id": "Dispositivo", - "name": "Nombre" - }, - "data_description": { - "name": "Dejar en blanco utilizará el nombre del dispositivo de serie" - } - }, - "entity": { - "data": { - "source_entity_id": "Entidad", - "name": "Nombre" - }, - "data_description": { - "name": "Dejar en blanco utilizará el nombre del dispositivo de serie" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Fabricante: {manufacturer}\nModelo: {model}\nID de Modelo: {model_id}\nVersión de Hardware: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Plantilla para determinar que una batería es baja, debe devolver verdadero si es baja\nSolo necesario para niveles de batería no estándar", "filter_outliers": "Filtra las grandes caídas del nivel de batería, reduciendo los eventos que se disparan falsamente en los dispositivos que informan erróneamente de los niveles de forma ocasional." } - }, - "manual": { - "description": "Este dispositivo está marcado en la librería como manual; las variantes usan diferentes tipos de baterías, por lo que no se puede configurar en la librería.\nEl siguiente paso te permitirá configurar tu tipo de batería, pero por favor, no envíes una solicitud de dispositivo.", - "title": "Configuración manual del dispositivo" } }, "abort": { - "already_configured": "Dispositivo ya configurado" + "already_configured": "Dispositivo ya configurado", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Se ha producido un error desconocido.", - "unconfigurable_entity": "No es posible añadir esta entidad a las Notas de la batería." + "unknown": "Se ha producido un error desconocido." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Si necesitas ayuda con la configuración, echa un vistazo aquí: {documentation_url}", + "data": { + "association_type": "Tipo de asociación" + }, + "menu_options": { + "device": "Dispositivo (recomendado)", + "entity": "Entidad" + }, + "title": "Elija su tipo de asociación" + }, + "device": { + "data": { + "device_id": "Dispositivo", + "name": "Nombre" + }, + "data_description": { + "name": "Dejar en blanco utilizará el nombre del dispositivo de serie" + } + }, + "entity": { + "data": { + "source_entity_id": "Entidad", + "name": "Nombre" + }, + "data_description": { + "name": "Dejar en blanco utilizará el nombre del dispositivo de serie" + } + }, + "battery": { + "description": "Fabricante: {manufacturer}\nModelo: {model}\nID de Modelo: {model_id}\nVersión de Hardware: {hw_version}", + "data": { + "battery_type": "Tipo de batería", + "battery_quantity": "Cantidad de batería", + "battery_low_threshold": "Umbral bajo de batería", + "battery_low_template": "Plantilla de batería baja", + "filter_outliers": "Filtrar valores atípicos" + }, + "data_description": { + "battery_low_threshold": "0 usará el umbral global por defecto", + "battery_low_template": "Plantilla para determinar que una batería es baja, debe devolver verdadero si es baja\nSolo necesario para niveles de batería no estándar", + "filter_outliers": "Filtra las grandes caídas del nivel de batería, reduciendo los eventos que se disparan falsamente en los dispositivos que informan erróneamente de los niveles de forma ocasional." + } + }, + "manual": { + "description": "Este dispositivo está marcado en la librería como manual; las variantes usan diferentes tipos de baterías, por lo que no se puede configurar en la librería.\nEl siguiente paso te permitirá configurar tu tipo de batería, pero por favor, no envíes una solicitud de dispositivo.", + "title": "Configuración manual del dispositivo" + }, + "reconfigure": { + "description": "Fabricante: {manufacturer}\nModelo: {model}\nID de Modelo: {model_id}\nVersión de Hardware: {hw_version}", + "data": { + "name": "Nombre", + "battery_type": "Tipo de batería", + "battery_quantity": "Cantidad de batería", + "battery_low_threshold": "Umbral bajo de batería", + "battery_low_template": "Plantilla de batería baja", + "filter_outliers": "Filtrar valores atípicos" + }, + "data_description": { + "name": "Dejar en blanco utilizará el nombre del dispositivo de serie", + "battery_low_threshold": "0 usará el umbral global por defecto", + "battery_low_template": "Plantilla para determinar que una batería es baja, debe devolver verdadero si es baja\nSolo necesario para niveles de batería no estándar", + "filter_outliers": "Filtra las grandes caídas del nivel de batería, reduciendo los eventos que se disparan falsamente en los dispositivos que informan erróneamente de los niveles de forma ocasional." + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Dispositivo ya configurado" + }, + "error": { + "unknown": "Se ha producido un error desconocido.", + "unconfigurable_entity": "No es posible añadir esta entidad a las Notas de la batería.", + "orphaned_battery_note": "El dispositivo o entidad asociada ya no existe para esta Nota de batería" + } } }, "options": { "step": { "init": { - "description": "Fabricante: {manufacturer}\nModelo: {model}\nID de Modelo: {model_id}\nVersión de Hardware: {hw_version}", "data": { - "name": "Nombre", - "battery_type": "Tipo de batería", - "battery_quantity": "Cantidad de batería", - "battery_low_threshold": "Umbral bajo de batería", - "battery_low_template": "Plantilla de batería baja", - "filter_outliers": "Filtrar valores atípicos" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Dejar en blanco utilizará el nombre del dispositivo de serie", - "battery_low_threshold": "0 usará el umbral global por defecto", - "battery_low_template": "Plantilla para determinar que una batería es baja, debe devolver verdadero si es baja\nSolo necesario para niveles de batería no estándar", - "filter_outliers": "Filtra las grandes caídas del nivel de batería, reduciendo los eventos que se disparan falsamente en los dispositivos que informan erróneamente de los niveles de forma ocasional." + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "El dispositivo o entidad asociada ya no existe para esta Nota de batería", "unknown": "Se ha producido un error desconocido." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/fi.json b/custom_components/battery_notes/translations/fi.json index d823695d2..c63f1c195 100644 --- a/custom_components/battery_notes/translations/fi.json +++ b/custom_components/battery_notes/translations/fi.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Jos tarvitset apua asetuksissa, katso täältä: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Association type" - }, - "menu_options": { - "device": "Laite (suositeltu)", - "entity": "Entiteetti" - }, - "title": "Choose your association type" - }, - "device": { - "data": { - "device_id": "Laite", - "name": "Nimi" - }, - "data_description": { - "name": "Tyhjäksi jättäminen ottaa nimen lähdelaitteesta" - } - }, - "entity": { - "data": { - "source_entity_id": "Entiteetti", - "name": "Nimi" - }, - "data_description": { - "name": "Tyhjäksi jättäminen ottaa nimen lähde entiteetistä" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", - "title": "Device manual configuration" } }, "abort": { - "already_configured": "Laite on jo määritelty" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Tuntematon virhe.", - "unconfigurable_entity": "It is not possible to add this entity to Battery Notes." + "unknown": "Tuntematon virhe." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Jos tarvitset apua asetuksissa, katso täältä: {documentation_url}", + "data": { + "association_type": "Association type" + }, + "menu_options": { + "device": "Laite (suositeltu)", + "entity": "Entiteetti" + }, + "title": "Choose your association type" + }, + "device": { + "data": { + "device_id": "Laite", + "name": "Nimi" + }, + "data_description": { + "name": "Tyhjäksi jättäminen ottaa nimen lähdelaitteesta" + } + }, + "entity": { + "data": { + "source_entity_id": "Entiteetti", + "name": "Nimi" + }, + "data_description": { + "name": "Tyhjäksi jättäminen ottaa nimen lähde entiteetistä" + } + }, + "battery": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "battery_type": "Akun tyyppi", + "battery_quantity": "Akkujen määrä", + "battery_low_threshold": "Akun alhainen raja", + "battery_low_template": "Battery low template", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 käyttää yleistä oletusarvoa", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", + "title": "Device manual configuration" + }, + "reconfigure": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "name": "Nimi", + "battery_type": "Akun tyyppi", + "battery_quantity": "Akkujen määrä", + "battery_low_threshold": "Akun alhainen raja", + "battery_low_template": "Battery low template", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Tyhjäksi jättäminen ottaa nimen lähdelaitteesta", + "battery_low_threshold": "0 käyttää yleistä oletusarvoa", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Laite on jo määritelty" + }, + "error": { + "unknown": "Tuntematon virhe.", + "unconfigurable_entity": "It is not possible to add this entity to Battery Notes.", + "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note." + } } }, "options": { "step": { "init": { - "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", "data": { - "name": "Nimi", - "battery_type": "Akun tyyppi", - "battery_quantity": "Akkujen määrä", - "battery_low_threshold": "Akun alhainen raja", - "battery_low_template": "Battery low template", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Tyhjäksi jättäminen ottaa nimen lähdelaitteesta", - "battery_low_threshold": "0 käyttää yleistä oletusarvoa", - "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.", "unknown": "Tuntematon virhe." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/fr.json b/custom_components/battery_notes/translations/fr.json index be8401481..ea40067dc 100644 --- a/custom_components/battery_notes/translations/fr.json +++ b/custom_components/battery_notes/translations/fr.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Si vous avez besoin d'aide pour la configuration, regardez ici : https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Type d'association:" - }, - "menu_options": { - "device": "Appareil (recommandé)", - "entity": "Entité" - }, - "title": "Choisissez votre type d'association" - }, - "device": { - "data": { - "device_id": "Appareil", - "name": "Nom" - }, - "data_description": { - "name": "Le nom par défaut sera utilisé si laissé vide" - } - }, - "entity": { - "data": { - "source_entity_id": "Entité", - "name": "Nom" - }, - "data_description": { - "name": "Le nom de l'entité sera utilisé si laissé vide" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Fabricant : {manufacturer}\nModèle : {model}\nID modèle : {model_id}\nVersion du matériel : {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Modèle pour déterminer si une batterie est faible, devrait retourner vrai si faible\nNécessaire uniquement pour les niveaux de batterie non standard", "filter_outliers": "Filtrer les baisses importantes du niveau de batterie, pour réduire les événements faussement déclenchés par les appareils qui signalent occasionnellement des niveaux erronés" } - }, - "manual": { - "description": "Cet appareil est marqué dans la bibliothèque comme manuel, les variantes utilisent des types de batterie différents, il ne peut donc pas être défini dans la bibliothèque.\nL'étape suivante vous permettra de définir votre type de batterie, mais veuillez ne pas soumettre de demande d'appareil.", - "title": "Configuration manuelle de l'appareil" } }, "abort": { - "already_configured": "L'entité est deja configurée" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Erreur inconnue.", - "unconfigurable_entity": "Il n'est pas possible d'ajouter cette entité à Battery Notes." + "unknown": "Erreur inconnue." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Si vous avez besoin d'aide pour la configuration, regardez ici : {documentation_url}", + "data": { + "association_type": "Type d'association:" + }, + "menu_options": { + "device": "Appareil (recommandé)", + "entity": "Entité" + }, + "title": "Choisissez votre type d'association" + }, + "device": { + "data": { + "device_id": "Appareil", + "name": "Nom" + }, + "data_description": { + "name": "Le nom par défaut sera utilisé si laissé vide" + } + }, + "entity": { + "data": { + "source_entity_id": "Entité", + "name": "Nom" + }, + "data_description": { + "name": "Le nom de l'entité sera utilisé si laissé vide" + } + }, + "battery": { + "description": "Fabricant : {manufacturer}\nModèle : {model}\nID modèle : {model_id}\nVersion du matériel : {hw_version}", + "data": { + "battery_type": "Type de batterie", + "battery_quantity": "Nombre de batteries", + "battery_low_threshold": "Seuil de batterie faible", + "battery_low_template": "Modèle de batterie faible", + "filter_outliers": "Filtrer les valeurs aberrantes" + }, + "data_description": { + "battery_low_threshold": "0 gardera le seuil par defaut", + "battery_low_template": "Modèle pour déterminer si une batterie est faible, devrait retourner vrai si faible\nNécessaire uniquement pour les niveaux de batterie non standard", + "filter_outliers": "Filtrer les baisses importantes du niveau de batterie, pour réduire les événements faussement déclenchés par les appareils qui signalent occasionnellement des niveaux erronés" + } + }, + "manual": { + "description": "Cet appareil est marqué dans la bibliothèque comme manuel, les variantes utilisent des types de batterie différents, il ne peut donc pas être défini dans la bibliothèque.\nL'étape suivante vous permettra de définir votre type de batterie, mais veuillez ne pas soumettre de demande d'appareil.", + "title": "Configuration manuelle de l'appareil" + }, + "reconfigure": { + "description": "Fabricant : {manufacturer}\nModèle : {model}\nID modèle : {model_id}\nVersion du matériel : {hw_version}", + "data": { + "name": "Nom", + "battery_type": "Type de batterie", + "battery_quantity": "Nombre de batteries", + "battery_low_threshold": "Seuil de batterie faible", + "battery_low_template": "Modèle de batterie faible", + "filter_outliers": "Filtrer les valeurs aberrantes" + }, + "data_description": { + "name": "Laisser vide gardera le seuil par defaut", + "battery_low_threshold": "0 gardera le seuil par defaut", + "battery_low_template": "Modèle pour déterminer si une batterie est faible, devrait retourner vrai si faible\nNécessaire uniquement pour les niveaux de batterie non standard", + "filter_outliers": "Filtrer les baisses importantes du niveau de batterie, pour réduire les événements faussement déclenchés par les appareils qui signalent occasionnellement des niveaux erronés" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "L'entité est deja configurée" + }, + "error": { + "unknown": "Erreur inconnue.", + "unconfigurable_entity": "Il n'est pas possible d'ajouter cette entité à Battery Notes.", + "orphaned_battery_note": "Le périphérique ou l'entité associée n'existe plus pour cette Note de Batterie." + } } }, "options": { "step": { "init": { - "description": "Fabricant : {manufacturer}\nModèle : {model}\nID modèle : {model_id}\nVersion du matériel : {hw_version}", "data": { - "name": "Nom", - "battery_type": "Type de batterie", - "battery_quantity": "Nombre de batteries", - "battery_low_threshold": "Seuil de batterie faible", - "battery_low_template": "Modèle de batterie faible", - "filter_outliers": "Filtrer les valeurs aberrantes" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Laisser vide gardera le seuil par defaut", - "battery_low_threshold": "0 gardera le seuil par defaut", - "battery_low_template": "Modèle pour déterminer si une batterie est faible, devrait retourner vrai si faible\nNécessaire uniquement pour les niveaux de batterie non standard", - "filter_outliers": "Filtrer les baisses importantes du niveau de batterie, pour réduire les événements faussement déclenchés par les appareils qui signalent occasionnellement des niveaux erronés" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Le périphérique ou l'entité associée n'existe plus pour cette Note de Batterie.", "unknown": "Erreur inconnue." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/hu.json b/custom_components/battery_notes/translations/hu.json index 28af9d3bf..dcb2d16bf 100644 --- a/custom_components/battery_notes/translations/hu.json +++ b/custom_components/battery_notes/translations/hu.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Segítség a konfigurációhoz: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Társítás típusa" - }, - "menu_options": { - "device": "Eszköz (javasolt)", - "entity": "Entitás" - }, - "title": "Válaszd ki a társítás típusát" - }, - "device": { - "data": { - "device_id": "Eszköz", - "name": "Név" - }, - "data_description": { - "name": "Üresen hagyva a forráseszköz nevét kapja" - } - }, - "entity": { - "data": { - "source_entity_id": "Entitás", - "name": "Név" - }, - "data_description": { - "name": "Üresen hagyva a forrás entitás nevét kapja" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Gyártó: {manufacturer}\nModell: {model}\nModell azonosító: {model_id}\nHardver verzió: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "A sablon igaz értéket kell adjon, ha az elem nemsokára lemerül\nCsak nem szokványos töltöttségi szint esetén kell megadni", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "Az eszköz különböző típusú elemekkel is használható, így manuálisan kell beállítani az elem típusát.\nA következő lépésben elvégezhető a beállítás, de kérlek, ne küldd be ezt az eszközt nekünk!", - "title": "Eszköz manuális beállítása" } }, "abort": { - "already_configured": "Az eszköz már konfigurálva van" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Ismeretlen hiba lépett fel.", - "unconfigurable_entity": "Ez az entitás nem adható a Battery Noteshoz." + "unknown": "Ismeretlen hiba lépett fel." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Segítség a konfigurációhoz: {documentation_url}", + "data": { + "association_type": "Társítás típusa" + }, + "menu_options": { + "device": "Eszköz (javasolt)", + "entity": "Entitás" + }, + "title": "Válaszd ki a társítás típusát" + }, + "device": { + "data": { + "device_id": "Eszköz", + "name": "Név" + }, + "data_description": { + "name": "Üresen hagyva a forráseszköz nevét kapja" + } + }, + "entity": { + "data": { + "source_entity_id": "Entitás", + "name": "Név" + }, + "data_description": { + "name": "Üresen hagyva a forrás entitás nevét kapja" + } + }, + "battery": { + "description": "Gyártó: {manufacturer}\nModell: {model}\nModell azonosító: {model_id}\nHardver verzió: {hw_version}", + "data": { + "battery_type": "Elemtípus", + "battery_quantity": "Elem darabszám", + "battery_low_threshold": "Alacsony elemszint küszöbérték", + "battery_low_template": "Alacsony elemszint sablon", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 érték esetén a központi beállítást fogja használni", + "battery_low_template": "A sablon igaz értéket kell adjon, ha az elem nemsokára lemerül\nCsak nem szokványos töltöttségi szint esetén kell megadni", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "Az eszköz különböző típusú elemekkel is használható, így manuálisan kell beállítani az elem típusát.\nA következő lépésben elvégezhető a beállítás, de kérlek, ne küldd be ezt az eszközt nekünk!", + "title": "Eszköz manuális beállítása" + }, + "reconfigure": { + "description": "Gyártó: {manufacturer}\nModell: {model}\nModell azonosító: {model_id}\nHardver verzió: {hw_version}", + "data": { + "name": "Név", + "battery_type": "Elemtípus", + "battery_quantity": "Elem darabszám", + "battery_low_threshold": "Alacsony elemszint küszöbérték", + "battery_low_template": "Alacsony elemszint sablon", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Üresen hagyva a forráseszköz nevét kapja", + "battery_low_threshold": "0 érték esetén a központi beállítást fogja használni", + "battery_low_template": "A sablon igaz értéket kell adjon, ha az elem nemsokára lemerül\nCsak nem szokványos töltöttségi szint esetén kell megadni", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Az eszköz már konfigurálva van" + }, + "error": { + "unknown": "Ismeretlen hiba lépett fel.", + "unconfigurable_entity": "Ez az entitás nem adható a Battery Noteshoz.", + "orphaned_battery_note": "Az ehhez a Battery Note-hoz társított eszköz vagy entitás már nem létezik." + } } }, "options": { "step": { "init": { - "description": "Gyártó: {manufacturer}\nModell: {model}\nModell azonosító: {model_id}\nHardver verzió: {hw_version}", "data": { - "name": "Név", - "battery_type": "Elemtípus", - "battery_quantity": "Elem darabszám", - "battery_low_threshold": "Alacsony elemszint küszöbérték", - "battery_low_template": "Alacsony elemszint sablon", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Üresen hagyva a forráseszköz nevét kapja", - "battery_low_threshold": "0 érték esetén a központi beállítást fogja használni", - "battery_low_template": "A sablon igaz értéket kell adjon, ha az elem nemsokára lemerül\nCsak nem szokványos töltöttségi szint esetén kell megadni", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Az ehhez a Battery Note-hoz társított eszköz vagy entitás már nem létezik.", "unknown": "Ismeretlen hiba lépett fel." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/it.json b/custom_components/battery_notes/translations/it.json index 91fabea66..174c9cf7a 100644 --- a/custom_components/battery_notes/translations/it.json +++ b/custom_components/battery_notes/translations/it.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Se hai bisogno di aiuto per la configurazione, dai un'occhiata qui: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Tipo di associazione" - }, - "menu_options": { - "device": "Dispositivo (consigliato)", - "entity": "Entità" - }, - "title": "Scegli il tipo di associazione" - }, - "device": { - "data": { - "device_id": "Dispositivo", - "name": "Nome" - }, - "data_description": { - "name": "Lasciando vuoto prenderà il nome dal dispositivo di origine" - } - }, - "entity": { - "data": { - "source_entity_id": "Entità", - "name": "Nome" - }, - "data_description": { - "name": "Lasciando vuoto prenderà il nome dall'entità di origine" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Produttore: {manufacturer}\nModello: {model}\nID Modello: {model_id}\nVersione hardware: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Modello per determinare se una batteria è scarica, dovrebbe restituire vero se scarica. \nNecessario solo per livelli di batteria non standard", "filter_outliers": "Filtra grossi cali batteria, riducendo falsi eventi su dispositivi che restituiscono occasionalmente livelli erronei" } - }, - "manual": { - "description": "Questo dispositivo è contrassegnato nella libreria come manuale, le varianti usano diversi tipi di batteria per cui non può essere impostato nella libreria.\nIl passo successivo ti permetterà di scegliere il tipo di batteria, non inviare una richiesta di dispositivo per favore.", - "title": "Configurazione manuale del dispositivo" } }, "abort": { - "already_configured": "Il dispositivo è già configurato" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Errore sconosciuto.", - "unconfigurable_entity": "Non è possibile aggiungere questa entità a Battery Notes." + "unknown": "Unknown error occurred." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Se hai bisogno di aiuto per la configurazione, dai un'occhiata qui: {documentation_url}", + "data": { + "association_type": "Tipo di associazione" + }, + "menu_options": { + "device": "Dispositivo (consigliato)", + "entity": "Entità" + }, + "title": "Scegli il tipo di associazione" + }, + "device": { + "data": { + "device_id": "Dispositivo", + "name": "Nome" + }, + "data_description": { + "name": "Lasciando vuoto prenderà il nome dal dispositivo di origine" + } + }, + "entity": { + "data": { + "source_entity_id": "Entità", + "name": "Nome" + }, + "data_description": { + "name": "Lasciando vuoto prenderà il nome dall'entità di origine" + } + }, + "battery": { + "description": "Produttore: {manufacturer}\nModello: {model}\nID Modello: {model_id}\nVersione hardware: {hw_version}", + "data": { + "battery_type": "Tipo di batteria", + "battery_quantity": "Quantità batteria", + "battery_low_threshold": "Livello batteria bassa", + "battery_low_template": "Modello batteria bassa", + "filter_outliers": "Filtra anomali" + }, + "data_description": { + "battery_low_threshold": "0 utilizzerà la soglia globale predefinita", + "battery_low_template": "Modello per determinare se una batteria è scarica, dovrebbe restituire vero se scarica. \nNecessario solo per livelli di batteria non standard", + "filter_outliers": "Filtra grossi cali batteria, riducendo falsi eventi su dispositivi che restituiscono occasionalmente livelli erronei" + } + }, + "manual": { + "description": "Questo dispositivo è contrassegnato nella libreria come manuale, le varianti usano diversi tipi di batteria per cui non può essere impostato nella libreria.\nIl passo successivo ti permetterà di scegliere il tipo di batteria, non inviare una richiesta di dispositivo per favore.", + "title": "Configurazione manuale del dispositivo" + }, + "reconfigure": { + "description": "Produttore: {manufacturer}\nModello: {model}\nID Modello: {model_id}\nVersione hardware: {hw_version}", + "data": { + "name": "Nome", + "battery_type": "Tipo di batteria", + "battery_quantity": "Quantità batteria", + "battery_low_threshold": "Livello batteria bassa", + "battery_low_template": "Modello batteria bassa", + "filter_outliers": "Filtra anomali" + }, + "data_description": { + "name": "Lasciando vuoto prenderà il nome dal dispositivo di origine", + "battery_low_threshold": "0 utilizzerà la soglia globale predefinita", + "battery_low_template": "Modello per determinare se una batteria è scarica, dovrebbe restituire vero se scarica. \nNecessario solo per livelli di batteria non standard", + "filter_outliers": "Filtra grossi cali batteria, riducendo falsi eventi su dispositivi che restituiscono occasionalmente livelli erronei" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Il dispositivo è già configurato" + }, + "error": { + "unknown": "Errore sconosciuto.", + "unconfigurable_entity": "Non è possibile aggiungere questa entità a Battery Notes.", + "orphaned_battery_note": "L'entità o dispositivo associato non esiste più per questo Battery Note." + } } }, "options": { "step": { "init": { - "description": "Produttore: {manufacturer}\nModello: {model}\nID Modello: {model_id}\nVersione hardware: {hw_version}", "data": { - "name": "Nome", - "battery_type": "Tipo di batteria", - "battery_quantity": "Quantità batteria", - "battery_low_threshold": "Livello batteria bassa", - "battery_low_template": "Modello batteria bassa", - "filter_outliers": "Filtra anomali" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Lasciando vuoto prenderà il nome dal dispositivo di origine", - "battery_low_threshold": "0 utilizzerà la soglia globale predefinita", - "battery_low_template": "Modello per determinare se una batteria è scarica, dovrebbe restituire vero se scarica. \nNecessario solo per livelli di batteria non standard", - "filter_outliers": "Filtra grossi cali batteria, riducendo falsi eventi su dispositivi che restituiscono occasionalmente livelli erronei" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "L'entità o dispositivo associato non esiste più per questo Battery Note.", "unknown": "Errore sconosciuto." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/lt.json b/custom_components/battery_notes/translations/lt.json index 1466fd31d..177dc4b18 100644 --- a/custom_components/battery_notes/translations/lt.json +++ b/custom_components/battery_notes/translations/lt.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Daugiau pagalbos apie konfigūraciją rasite čia: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Association type" - }, - "menu_options": { - "device": "Device (recommended)", - "entity": "Entity" - }, - "title": "Choose your association type" - }, - "device": { - "data": { - "device_id": "Prietaisas", - "name": "Pavadinimas" - }, - "data_description": { - "name": "Leaving blank will take the name from the source device" - } - }, - "entity": { - "data": { - "source_entity_id": "Entity", - "name": "Pavadinimas" - }, - "data_description": { - "name": "Leaving blank will take the name from the source entity" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", - "title": "Device manual configuration" } }, "abort": { - "already_configured": "Prietaisas jau sukonfigūruotas" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Įvyko nežinoma klaida.", - "unconfigurable_entity": "It is not possible to add this entity to Battery Notes." + "unknown": "Įvyko nežinoma klaida." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Daugiau pagalbos apie konfigūraciją rasite čia: {documentation_url}", + "data": { + "association_type": "Association type" + }, + "menu_options": { + "device": "Device (recommended)", + "entity": "Entity" + }, + "title": "Choose your association type" + }, + "device": { + "data": { + "device_id": "Prietaisas", + "name": "Pavadinimas" + }, + "data_description": { + "name": "Leaving blank will take the name from the source device" + } + }, + "entity": { + "data": { + "source_entity_id": "Entity", + "name": "Pavadinimas" + }, + "data_description": { + "name": "Leaving blank will take the name from the source entity" + } + }, + "battery": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "battery_type": "Baterijos tipas", + "battery_quantity": "Baterijų kiekis", + "battery_low_threshold": "Battery low threshold", + "battery_low_template": "Battery low template", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "Įrašius 0 bus naudojama numatytoji vertė", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", + "title": "Device manual configuration" + }, + "reconfigure": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "name": "Pavadinimas", + "battery_type": "Baterijos tipas", + "battery_quantity": "Baterijų kiekis", + "battery_low_threshold": "Battery low threshold", + "battery_low_template": "Battery low template", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Leaving blank will take the name from the source device", + "battery_low_threshold": "Įrašius 0 bus naudojama numatytoji vertė", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Prietaisas jau sukonfigūruotas" + }, + "error": { + "unknown": "Įvyko nežinoma klaida.", + "unconfigurable_entity": "It is not possible to add this entity to Battery Notes.", + "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note." + } } }, "options": { "step": { "init": { - "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", "data": { - "name": "Pavadinimas", - "battery_type": "Baterijos tipas", - "battery_quantity": "Baterijų kiekis", - "battery_low_threshold": "Battery low threshold", - "battery_low_template": "Battery low template", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Leaving blank will take the name from the source device", - "battery_low_threshold": "Įrašius 0 bus naudojama numatytoji vertė", - "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.", "unknown": "Įvyko nežinoma klaida." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/lv.json b/custom_components/battery_notes/translations/lv.json index 2804fda1e..d6bac8a4e 100644 --- a/custom_components/battery_notes/translations/lv.json +++ b/custom_components/battery_notes/translations/lv.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Ja nepieciešama konfigurēšanas palīdzība, papildus informāciju var iegūt šeit: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Asociācijas veids" - }, - "menu_options": { - "device": "Ierīce (ieteicams)", - "entity": "Vienība" - }, - "title": "Norādiet asociācijas veidu" - }, - "device": { - "data": { - "device_id": "Ierīce", - "name": "Nosaukums" - }, - "data_description": { - "name": "Atstājot tukšu tiks ņemts nosaukums no pamata ierīces" - } - }, - "entity": { - "data": { - "source_entity_id": "Vienība", - "name": "Nosaukums" - }, - "data_description": { - "name": "Atstājot tukšu tiks ņemts nosaukums no pamata vienības" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Ražotājs: {manufacturer}\nModelis: {model}\nModeļa ID: {model_id}\nAparatūras versija: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Šablons lai noteiktu zemu baterijas līmeni, kam jāatgriež patiesa vērtība, līmenis ir zems\nNepieciešams tikai nestandarta bateriju līmeņiem", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "Iekārta bibliotekā ir atzīmēta kā manuāla, kas nozīmē, ka šīs ierīces paveidi izmanto dažādus bateriju tipus.\nNākošājā solī norādiet kādu bateriju tipu ierīce izmanto. ", - "title": "Ierīces manuāla konfigurācija" } }, "abort": { - "already_configured": "Ierīce jau ir konfigurēta" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Notika nezināma kļūda.", - "unconfigurable_entity": "Nav iespējams pievienot šo vienību baterijas piezīmēm." + "unknown": "Notika nezināma kļūda." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Ja nepieciešama konfigurēšanas palīdzība, papildus informāciju var iegūt šeit: {documentation_url}", + "data": { + "association_type": "Asociācijas veids" + }, + "menu_options": { + "device": "Ierīce (ieteicams)", + "entity": "Vienība" + }, + "title": "Norādiet asociācijas veidu" + }, + "device": { + "data": { + "device_id": "Ierīce", + "name": "Nosaukums" + }, + "data_description": { + "name": "Atstājot tukšu tiks ņemts nosaukums no pamata ierīces" + } + }, + "entity": { + "data": { + "source_entity_id": "Vienība", + "name": "Nosaukums" + }, + "data_description": { + "name": "Atstājot tukšu tiks ņemts nosaukums no pamata vienības" + } + }, + "battery": { + "description": "Ražotājs: {manufacturer}\nModelis: {model}\nModeļa ID: {model_id}\nAparatūras versija: {hw_version}", + "data": { + "battery_type": "Baterijas tips", + "battery_quantity": "Bateriju daudzums", + "battery_low_threshold": "Zema baterijas līmeņa slieksnis", + "battery_low_template": "Zema baterijas līmeņa šablons", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "Norādiet 0, lai izmantotu noklusēto slieksni", + "battery_low_template": "Šablons lai noteiktu zemu baterijas līmeni, kam jāatgriež patiesa vērtība, līmenis ir zems\nNepieciešams tikai nestandarta bateriju līmeņiem", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "Iekārta bibliotekā ir atzīmēta kā manuāla, kas nozīmē, ka šīs ierīces paveidi izmanto dažādus bateriju tipus.\nNākošājā solī norādiet kādu bateriju tipu ierīce izmanto. ", + "title": "Ierīces manuāla konfigurācija" + }, + "reconfigure": { + "description": "Ražotājs: {manufacturer}\nModelis: {model}\nModeļa ID: {model_id}\nAparatūras versija: {hw_version}", + "data": { + "name": "Nosaukums", + "battery_type": "Baterijas tips", + "battery_quantity": "Bateriju daudzums", + "battery_low_threshold": "Zema baterijas līmeņa slieksnis", + "battery_low_template": "Zema baterijas līmeņa šablons", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Atstājot tukšu tiks ņemts nosaukums no pamata ierīces", + "battery_low_threshold": "Norādiet 0, lai izmantotu noklusēto slieksni", + "battery_low_template": "Šablons lai noteiktu zemu baterijas līmeni, kam jāatgriež patiesa vērtība, līmenis ir zems\nNepieciešams tikai nestandarta bateriju līmeņiem", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Ierīce jau ir konfigurēta" + }, + "error": { + "unknown": "Notika nezināma kļūda.", + "unconfigurable_entity": "Nav iespējams pievienot šo vienību baterijas piezīmēm.", + "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note." + } } }, "options": { "step": { "init": { - "description": "Ražotājs: {manufacturer}\nModelis: {model}\nModeļa ID: {model_id}\nAparatūras versija: {hw_version}", "data": { - "name": "Nosaukums", - "battery_type": "Baterijas tips", - "battery_quantity": "Bateriju daudzums", - "battery_low_threshold": "Zema baterijas līmeņa slieksnis", - "battery_low_template": "Zema baterijas līmeņa šablons", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Atstājot tukšu tiks ņemts nosaukums no pamata ierīces", - "battery_low_threshold": "Norādiet 0, lai izmantotu noklusēto slieksni", - "battery_low_template": "Šablons lai noteiktu zemu baterijas līmeni, kam jāatgriež patiesa vērtība, līmenis ir zems\nNepieciešams tikai nestandarta bateriju līmeņiem", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.", "unknown": "Notika nezināma kļūda." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/nl.json b/custom_components/battery_notes/translations/nl.json index 5cdd02b40..193e57346 100644 --- a/custom_components/battery_notes/translations/nl.json +++ b/custom_components/battery_notes/translations/nl.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Als u hulp nodig heeft bij de configuratie kunt u hier een kijkje nemen: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Associatietype" - }, - "menu_options": { - "device": "Apparaat (aanbevolen)", - "entity": "Entiteit" - }, - "title": "Kies uw associatietype" - }, - "device": { - "data": { - "device_id": "Apparaat", - "name": "Naam" - }, - "data_description": { - "name": "Indien dit leeg gelaten wordt dan wordt de naam van het bronapparaat overgenomen" - } - }, - "entity": { - "data": { - "source_entity_id": "Entiteit", - "name": "Naam" - }, - "data_description": { - "name": "Indien dit leeg gelaten wordt dan wordt de naam van de bron entiteit overgenomen" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Fabrikant: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware versie: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Sjabloon om te bepalen dat een batterij bijna leeg is, zal 'waar' teruggeven als bijna leeg\nAlleen nodig voor niet-standaard batterijniveaus", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "Dit apparaat is in de bibliotheek gemarkeerd als handmatig, varianten gebruiken verschillende batterijtypen zodat het niet kan worden ingesteld in de bibliotheek.\nDe volgende stap staat je toe om je batterijtype in te stellen, maar gelieve geen apparaat verzoek in te dienen.", - "title": "Handmatige configuratie van het toestel" } }, "abort": { - "already_configured": "Apparaat is al geconfigureerd" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Onbekende fout opgetreden.", - "unconfigurable_entity": "Het is niet mogelijk om deze entiteit toe te voegen aan Battery Notes" + "unknown": "Onbekende fout opgetreden." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Als u hulp nodig heeft bij de configuratie kunt u hier een kijkje nemen: {documentation_url}", + "data": { + "association_type": "Associatietype" + }, + "menu_options": { + "device": "Apparaat (aanbevolen)", + "entity": "Entiteit" + }, + "title": "Kies uw associatietype" + }, + "device": { + "data": { + "device_id": "Apparaat", + "name": "Naam" + }, + "data_description": { + "name": "Indien dit leeg gelaten wordt dan wordt de naam van het bronapparaat overgenomen" + } + }, + "entity": { + "data": { + "source_entity_id": "Entiteit", + "name": "Naam" + }, + "data_description": { + "name": "Indien dit leeg gelaten wordt dan wordt de naam van de bron entiteit overgenomen" + } + }, + "battery": { + "description": "Fabrikant: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware versie: {hw_version}", + "data": { + "battery_type": "Batterij type", + "battery_quantity": "Aantal batterijen", + "battery_low_threshold": "Drempelwaarde batterij bijna leeg", + "battery_low_template": "Sjabloon batterij bijna leeg", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 zal de globale standaard drempelwaarde gebruiken", + "battery_low_template": "Sjabloon om te bepalen dat een batterij bijna leeg is, zal 'waar' teruggeven als bijna leeg\nAlleen nodig voor niet-standaard batterijniveaus", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "Dit apparaat is in de bibliotheek gemarkeerd als handmatig, varianten gebruiken verschillende batterijtypen zodat het niet kan worden ingesteld in de bibliotheek.\nDe volgende stap staat je toe om je batterijtype in te stellen, maar gelieve geen apparaat verzoek in te dienen.", + "title": "Handmatige configuratie van het toestel" + }, + "reconfigure": { + "description": "Fabrikant: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware versie: {hw_version}", + "data": { + "name": "Naam", + "battery_type": "Batterij type", + "battery_quantity": "Aantal batterijen", + "battery_low_threshold": "Drempelwaarde batterij bijna leeg", + "battery_low_template": "Sjabloon batterij bijna leeg", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Indien dit leeg gelaten wordt dan wordt de naam van het bronapparaat overgenomen", + "battery_low_threshold": "0 zal de globale standaard drempelwaarde gebruiken", + "battery_low_template": "Sjabloon om te bepalen dat een batterij bijna leeg is, zal 'waar' teruggeven als bijna leeg\nAlleen nodig voor niet-standaard batterijniveaus", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "unknown": "Onbekende fout opgetreden.", + "unconfigurable_entity": "Het is niet mogelijk om deze entiteit toe te voegen aan Battery Notes", + "orphaned_battery_note": "Het gekoppelde apparaat of entiteit bestaat niet meer voor deze Battery Note." + } } }, "options": { "step": { "init": { - "description": "Fabrikant: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware versie: {hw_version}", "data": { - "name": "Naam", - "battery_type": "Batterij type", - "battery_quantity": "Aantal batterijen", - "battery_low_threshold": "Drempelwaarde batterij bijna leeg", - "battery_low_template": "Sjabloon batterij bijna leeg", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Indien dit leeg gelaten wordt dan wordt de naam van het bronapparaat overgenomen", - "battery_low_threshold": "0 zal de globale standaard drempelwaarde gebruiken", - "battery_low_template": "Sjabloon om te bepalen dat een batterij bijna leeg is, zal 'waar' teruggeven als bijna leeg\nAlleen nodig voor niet-standaard batterijniveaus", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Het gekoppelde apparaat of entiteit bestaat niet meer voor deze Battery Note.", "unknown": "Onbekende fout opgetreden." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/no.json b/custom_components/battery_notes/translations/no.json index 43ddb80a1..90650c09a 100644 --- a/custom_components/battery_notes/translations/no.json +++ b/custom_components/battery_notes/translations/no.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Hvis du trenger hjelp med konfigurasjonen, kan du se her: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Association type" - }, - "menu_options": { - "device": "Device (recommended)", - "entity": "Entity" - }, - "title": "Choose your association type" - }, - "device": { - "data": { - "device_id": "Device", - "name": "Navn" - }, - "data_description": { - "name": "Lar du det stå tomt, vil ta navnet fra kildeenheten bli brukt" - } - }, - "entity": { - "data": { - "source_entity_id": "Entity", - "name": "Navn" - }, - "data_description": { - "name": "Leaving blank will take the name from the source entity" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Produsent: {manufacturer}\nModel: {model}\nModell-ID: {model_id}\nMaskinvareversjon: {hw_version}", @@ -44,43 +19,137 @@ "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", - "title": "Manuell konfigurasjon for enhet" } }, "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "En ukjent feil har oppstått.", - "unconfigurable_entity": "Det er ikke mulig å legge denne enheten til batterinotater." + "unknown": "En ukjent feil har oppstått." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Hvis du trenger hjelp med konfigurasjonen, kan du se her: {documentation_url}", + "data": { + "association_type": "Association type" + }, + "menu_options": { + "device": "Device (recommended)", + "entity": "Entity" + }, + "title": "Choose your association type" + }, + "device": { + "data": { + "device_id": "Device", + "name": "Navn" + }, + "data_description": { + "name": "Lar du det stå tomt, vil ta navnet fra kildeenheten bli brukt" + } + }, + "entity": { + "data": { + "source_entity_id": "Entity", + "name": "Navn" + }, + "data_description": { + "name": "Leaving blank will take the name from the source entity" + } + }, + "battery": { + "description": "Produsent: {manufacturer}\nModel: {model}\nModell-ID: {model_id}\nMaskinvareversjon: {hw_version}", + "data": { + "battery_type": "Batteritype", + "battery_quantity": "Antall batterier", + "battery_low_threshold": "Lavt batterinivå-terskel", + "battery_low_template": "Mal for lavt batterinivå", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 will use the global default threshold", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", + "title": "Manuell konfigurasjon for enhet" + }, + "reconfigure": { + "description": "Produsent: {manufacturer}\nModel: {model}\nModell-ID: {model_id}\nMaskinvareversjon: {hw_version}", + "data": { + "name": "Navn", + "battery_type": "Batteritype", + "battery_quantity": "Antall batterier", + "battery_low_threshold": "Lavt batterinivå-terskel", + "battery_low_template": "Mal for lavt batterinivå", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Leaving blank will take the name from the source device", + "battery_low_threshold": "0 will use the global default threshold", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Enheten er allerede konfigurert" + }, + "error": { + "unknown": "En ukjent feil har oppstått.", + "unconfigurable_entity": "Det er ikke mulig å legge denne enheten til batterinotater.", + "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note." + } } }, "options": { "step": { "init": { - "description": "Produsent: {manufacturer}\nModel: {model}\nModell-ID: {model_id}\nMaskinvareversjon: {hw_version}", "data": { - "name": "Navn", - "battery_type": "Batteritype", - "battery_quantity": "Antall batterier", - "battery_low_threshold": "Lavt batterinivå-terskel", - "battery_low_template": "Mal for lavt batterinivå", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Leaving blank will take the name from the source device", - "battery_low_threshold": "0 will use the global default threshold", - "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.", - "unknown": "Unknown error occurred." + "unknown": "En ukjent feil har oppstått." } }, "entity": { @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/pl.json b/custom_components/battery_notes/translations/pl.json index 38f5e770a..b31c9e594 100644 --- a/custom_components/battery_notes/translations/pl.json +++ b/custom_components/battery_notes/translations/pl.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Jeśli potrzebujesz pomocy w konfiguracji, zajrzyj tutaj: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Rodzaj powiązania" - }, - "menu_options": { - "device": "Urządzenie (zalecane)", - "entity": "Encja" - }, - "title": "Wybierz rodzaj powiązania" - }, - "device": { - "data": { - "device_id": "Urządzenie", - "name": "Nazwa" - }, - "data_description": { - "name": "Pozostawienie pustego pola spowoduje pobranie nazwy z urządzenia źródłowego" - } - }, - "entity": { - "data": { - "source_entity_id": "Encja", - "name": "Nazwa" - }, - "data_description": { - "name": "Pozostawienie pustego pola spowoduje pobranie nazwy z encji źródłowej" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Producent: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nWersja sprzętowa: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Szablon do określenia czy poziom naładowania baterii jest niski, powinien zwrócić wartość true, jeśli poziom jest niski.\nJest wymagany tylko dla niestandardowych raportów poziomu baterii", "filter_outliers": "Filtruj duże spadki poziomu baterii, zmniejszając fałszywe wyzwalanie zdarzeń na urządzeniach, które sporadycznie zgłaszają poziomy baterii" } - }, - "manual": { - "description": "To urządzenie jest oznaczone w bibliotece jako manualne, różne warianty używają różnych typów baterii, więc nie mogą być ustawione z biblioteki.\nNastępny krok pozwoli Ci ustawić typ baterii, ale nie wysyłaj żądania dodania urządzenia do biblioteki.", - "title": "Ręczna konfiguracja urządzenia" } }, "abort": { - "already_configured": "Urządzenie jest już skonfigurowane" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Wystąpił nieznany błąd.", - "unconfigurable_entity": "Nie można dodać tej encji do Battery Notes." + "unknown": "Wystąpił nieznany błąd." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Jeśli potrzebujesz pomocy w konfiguracji, zajrzyj tutaj: {documentation_url}", + "data": { + "association_type": "Rodzaj powiązania" + }, + "menu_options": { + "device": "Urządzenie (zalecane)", + "entity": "Encja" + }, + "title": "Wybierz rodzaj powiązania" + }, + "device": { + "data": { + "device_id": "Urządzenie", + "name": "Nazwa" + }, + "data_description": { + "name": "Pozostawienie pustego pola spowoduje pobranie nazwy z urządzenia źródłowego" + } + }, + "entity": { + "data": { + "source_entity_id": "Encja", + "name": "Nazwa" + }, + "data_description": { + "name": "Pozostawienie pustego pola spowoduje pobranie nazwy z encji źródłowej" + } + }, + "battery": { + "description": "Producent: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nWersja sprzętowa: {hw_version}", + "data": { + "battery_type": "Typ baterii", + "battery_quantity": "Liczba baterii", + "battery_low_threshold": "Próg niskiego poziomu baterii", + "battery_low_template": "Szablon niskiego poziomu baterii", + "filter_outliers": "Filtr wartości odstających" + }, + "data_description": { + "battery_low_threshold": "0 użyje globalnego progu domyślnego", + "battery_low_template": "Szablon do określenia czy poziom naładowania baterii jest niski, powinien zwrócić wartość true, jeśli poziom jest niski.\nJest wymagany tylko dla niestandardowych raportów poziomu baterii", + "filter_outliers": "Filtruj duże spadki poziomu baterii, zmniejszając fałszywe wyzwalanie zdarzeń na urządzeniach, które sporadycznie zgłaszają poziomy baterii" + } + }, + "manual": { + "description": "To urządzenie jest oznaczone w bibliotece jako manualne, różne warianty używają różnych typów baterii, więc nie mogą być ustawione z biblioteki.\nNastępny krok pozwoli Ci ustawić typ baterii, ale nie wysyłaj żądania dodania urządzenia do biblioteki.", + "title": "Ręczna konfiguracja urządzenia" + }, + "reconfigure": { + "description": "Producent: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nWersja sprzętowa: {hw_version}", + "data": { + "name": "Nazwa", + "battery_type": "Typ baterii", + "battery_quantity": "Liczba baterii", + "battery_low_threshold": "Próg niskiego poziomu baterii", + "battery_low_template": "Szablon niskiego poziomu baterii", + "filter_outliers": "Filtr wartości odstających" + }, + "data_description": { + "name": "Pozostawienie pustego pola spowoduje pobranie nazwy z urządzenia źródłowego", + "battery_low_threshold": "0 użyje globalnego progu domyślnego", + "battery_low_template": "Szablon do określenia czy poziom naładowania baterii jest niski, powinien zwrócić wartość true, jeśli poziom jest niski.\nJest wymagany tylko dla niestandardowych raportów poziomu baterii", + "filter_outliers": "Filtruj duże spadki poziomu baterii, zmniejszając fałszywe wyzwalanie zdarzeń na urządzeniach, które sporadycznie zgłaszają poziomy baterii" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Urządzenie jest już skonfigurowane" + }, + "error": { + "unknown": "Wystąpił nieznany błąd.", + "unconfigurable_entity": "Nie można dodać tej encji do Battery Notes.", + "orphaned_battery_note": "Powiązane urządzenie lub encja już nie istnieje dla tej Notatki Baterii." + } } }, "options": { "step": { "init": { - "description": "Producent: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nWersja sprzętowa: {hw_version}", "data": { - "name": "Nazwa", - "battery_type": "Typ baterii", - "battery_quantity": "Liczba baterii", - "battery_low_threshold": "Próg niskiego poziomu baterii", - "battery_low_template": "Szablon niskiego poziomu baterii", - "filter_outliers": "Filtr wartości odstających" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Pozostawienie pustego pola spowoduje pobranie nazwy z urządzenia źródłowego", - "battery_low_threshold": "0 użyje globalnego progu domyślnego", - "battery_low_template": "Szablon do określenia czy poziom naładowania baterii jest niski, powinien zwrócić wartość true, jeśli poziom jest niski.\nJest wymagany tylko dla niestandardowych raportów poziomu baterii", - "filter_outliers": "Filtruj duże spadki poziomu baterii, zmniejszając fałszywe wyzwalanie zdarzeń na urządzeniach, które sporadycznie zgłaszają poziomy baterii" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Powiązane urządzenie lub encja już nie istnieje dla tej Notatki Baterii.", "unknown": "Wystąpił nieznany błąd." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/pt-BR.json b/custom_components/battery_notes/translations/pt-BR.json index f4ee58ad3..e73f7029e 100644 --- a/custom_components/battery_notes/translations/pt-BR.json +++ b/custom_components/battery_notes/translations/pt-BR.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Se você precisar de ajuda com a configuração dê uma olhada aqui: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Tipos de Associação" - }, - "menu_options": { - "device": "Dispositivo (recomendado)", - "entity": "Entidade" - }, - "title": "Escolha o seu tipo de Associação" - }, - "device": { - "data": { - "device_id": "Dispositivo", - "name": "Nome" - }, - "data_description": { - "name": "Deixar em branco utilizará o nome do dispositivo de origem" - } - }, - "entity": { - "data": { - "source_entity_id": "Entidade", - "name": "Nome" - }, - "data_description": { - "name": "Deixar em branco levará o nome da entidade de origem" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Fabricante: {manufacturer}\nModelo: {model}\nModelo ID: {model_id}\nVersão de hardware: {hw_version}", @@ -44,43 +19,137 @@ "battery_low_template": "Modelo para determinar se a bateria está fraca, deve retornar verdadeiro se estiver fraca\nNecessário apenas para níveis de bateria fora do padrão", "filter_outliers": "Filtre grandes quedas no nível da bateria, reduzindo eventos de disparo falso em dispositivos que ocasionalmente relatam níveis incorretos." } - }, - "manual": { - "description": "Esse dispositivo está marcado na biblioteca como manual, variantes usam diferentes tipos de bateria, então o dispositivo não pode ser definido na biblioteca.\nO próximo passo permitirá que você defina o tipo de bateria, mas por favor, não envie uma solicitação de dispositivo.", - "title": "Configuração manual do dispositivo" } }, "abort": { - "already_configured": "O dispositivo já está configurado" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Ocorreu um erro desconhecido.", - "unconfigurable_entity": "Não é possível adicionar esta entidade ao Battery Notes." + "unknown": "Ocorreu um erro desconhecido." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Se você precisar de ajuda com a configuração dê uma olhada aqui: {documentation_url}", + "data": { + "association_type": "Tipos de Associação" + }, + "menu_options": { + "device": "Dispositivo (recomendado)", + "entity": "Entidade" + }, + "title": "Escolha o seu tipo de Associação" + }, + "device": { + "data": { + "device_id": "Dispositivo", + "name": "Nome" + }, + "data_description": { + "name": "Deixar em branco utilizará o nome do dispositivo de origem" + } + }, + "entity": { + "data": { + "source_entity_id": "Entidade", + "name": "Nome" + }, + "data_description": { + "name": "Deixar em branco levará o nome da entidade de origem" + } + }, + "battery": { + "description": "Fabricante: {manufacturer}\nModelo: {model}\nModelo ID: {model_id}\nVersão de hardware: {hw_version}", + "data": { + "battery_type": "Tipo de bateria", + "battery_quantity": "Quantidade de baterias", + "battery_low_threshold": "Limite de bateria fraca", + "battery_low_template": "Modelo de bateria fraca", + "filter_outliers": "Filtrar atípicos" + }, + "data_description": { + "battery_low_threshold": "0 irá usar o limite padrão global", + "battery_low_template": "Modelo para determinar se a bateria está fraca, deve retornar verdadeiro se estiver fraca\nNecessário apenas para níveis de bateria fora do padrão", + "filter_outliers": "Filtre grandes quedas no nível da bateria, reduzindo eventos de disparo falso em dispositivos que ocasionalmente relatam níveis incorretos." + } + }, + "manual": { + "description": "Esse dispositivo está marcado na biblioteca como manual, variantes usam diferentes tipos de bateria, então o dispositivo não pode ser definido na biblioteca.\nO próximo passo permitirá que você defina o tipo de bateria, mas por favor, não envie uma solicitação de dispositivo.", + "title": "Configuração manual do dispositivo" + }, + "reconfigure": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "name": "Name", + "battery_type": "Battery type", + "battery_quantity": "Battery quantity", + "battery_low_threshold": "Battery low threshold", + "battery_low_template": "Battery low template", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Leaving blank will take the name from the source device", + "battery_low_threshold": "0 will use the global default threshold", + "battery_low_template": "Template to determine a battery is low, should return true if low\nOnly needed for non-standard battery levels", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "O dispositivo já está configurado" + }, + "error": { + "unknown": "Ocorreu um erro desconhecido.", + "unconfigurable_entity": "Não é possível adicionar esta entidade ao Battery Notes.", + "orphaned_battery_note": "O dispositivo ou entidade associados não existe mais para o Battery Notes." + } } }, "options": { "step": { "init": { - "description": "Fabricante: {manufacturer}\nModelo: {model}\nModelo ID: {model_id}\nVersão de hardware: {hw_version}", "data": { - "name": "Nome", - "battery_type": "Tipo de bateria", - "battery_quantity": "Quantidade de baterias", - "battery_low_threshold": "Limite de bateria fraca", - "battery_low_template": "Modelo de bateria fraca", - "filter_outliers": "Filtros atípicos" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Deixar em branco utilizará o nome do dispositivo de origem", - "battery_low_threshold": "0 irá usar o limite padrão global", - "battery_low_template": "Modelo para determinar se a bateria está fraca, deve retornar verdadeiro se estiver fraca\nNecessário apenas para níveis de bateria fora do padrão", - "filter_outliers": "Filtre grandes quedas no nível da bateria, reduzindo eventos de disparo falso em dispositivos que ocasionalmente relatam níveis incorretos." + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "O dispositivo ou entidade associados não existe mais para o Battery Notes.", - "unknown": "Ocorreu um erro desconhecido." + "unknown": "Unknown error occurred." } }, "entity": { @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/pt.json b/custom_components/battery_notes/translations/pt.json index 2e5e40256..9276280d2 100644 --- a/custom_components/battery_notes/translations/pt.json +++ b/custom_components/battery_notes/translations/pt.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Caso precise se ajude na configuração pode verificar em : https://github.com/andrew-codechimp/ha-battery-notes", - "data": { - "association_type": "Tipo de associação" - }, - "menu_options": { - "device": "Dispositivo (recomendado)", - "entity": "Entidade" - }, - "title": "Escolha o tipo de associação" - }, - "device": { - "data": { - "device_id": "Dispositivo", - "name": "Nome" - }, - "data_description": { - "name": "Deixar em branco fará com que tenha o mesmo nome que o dispositivo de origem" - } - }, - "entity": { - "data": { - "source_entity_id": "Entidade", - "name": "Nome" - }, - "data_description": { - "name": "Deixar em branco fará com que tenha mesmo nome que a entidade de origem" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Fabricante: {manufacturer}\nModelo: {model}\nID do Modelo: {model_id}\nVersão de Hardware: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Template para determinar que a bateria está baixa, deve devolver true se estiver baixa\nÉ somente necessário para níveis de bateria non-standard", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "Este dispositivo está definido como criado manualmente, variantes usam tipos de bateria diferentes, portanto, não pode ser configurado na biblioteca. \n O próximo passo permitirá que defina o tipo de bateria, mas, por favor, não envie um pedido para o dispositivo.", - "title": "Configuração manual do dispositivo" } }, "abort": { - "already_configured": "Dispositivo já configurado" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Erro desconhecido.", - "unconfigurable_entity": "Não é possível adicionar esta entidade ao Battery Notes." + "unknown": "Erro desconhecido." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Caso precise se ajude na configuração pode verificar em : https://github.com/andrew-codechimp/ha-battery-notes", + "data": { + "association_type": "Tipo de associação" + }, + "menu_options": { + "device": "Dispositivo (recomendado)", + "entity": "Entidade" + }, + "title": "Escolha o tipo de associação" + }, + "device": { + "data": { + "device_id": "Dispositivo", + "name": "Nome" + }, + "data_description": { + "name": "Deixar em branco fará com que tenha o mesmo nome que o dispositivo de origem" + } + }, + "entity": { + "data": { + "source_entity_id": "Entidade", + "name": "Nome" + }, + "data_description": { + "name": "Deixar em branco fará com que tenha mesmo nome que a entidade de origem" + } + }, + "battery": { + "description": "Fabricante: {manufacturer}\nModelo: {model}\nID do Modelo: {model_id}\nVersão de Hardware: {hw_version}", + "data": { + "battery_type": "Tipo de bateria", + "battery_quantity": "Quantidade de baterias", + "battery_low_threshold": "Nivel de bateria baixa", + "battery_low_template": "Template de bateria baixa", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 irá ser usado como valor por defeito para definir o descarregada", + "battery_low_template": "Template para determinar que a bateria está baixa, deve devolver true se estiver baixa\nÉ somente necessário para níveis de bateria non-standard", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "Este dispositivo está definido como criado manualmente, variantes usam tipos de bateria diferentes, portanto, não pode ser configurado na biblioteca. \n O próximo passo permitirá que defina o tipo de bateria, mas, por favor, não envie um pedido para o dispositivo.", + "title": "Configuração manual do dispositivo" + }, + "reconfigure": { + "description": "Fabricante: {manufacturer}\nModelo: {model}\nID do Modelo: {model_id}\nVersão de Hardware: {hw_version}", + "data": { + "name": "Nome", + "battery_type": "Tipo de bateria", + "battery_quantity": "Quantidade de baterias", + "battery_low_threshold": "Nivel de bateria baixa", + "battery_low_template": "Template de bateria baixa", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Deixar em branco fará com que tenha mesmo o nome que o dispositivo de origem", + "battery_low_threshold": "0 irá ser usado como valor por defeito para definir o descarregada", + "battery_low_template": "Template para determinar que a bateria está baixa, deve devolver true se estiver baixa\nÉ somente necessário para níveis de bateria non-standard", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Dispositivo já configurado" + }, + "error": { + "unknown": "Erro desconhecido.", + "unconfigurable_entity": "Não é possível adicionar esta entidade ao Battery Notes.", + "orphaned_battery_note": "O dispositivo ou entidade associado não existe mais para esta Nota de Bateria." + } } }, "options": { "step": { "init": { - "description": "Fabricante: {manufacturer}\nModelo: {model}\nID do Modelo: {model_id}\nVersão de Hardware: {hw_version}", "data": { - "name": "Nome", - "battery_type": "Tipo de bateria", - "battery_quantity": "Quantidade de baterias", - "battery_low_threshold": "Nivel de bateria baixa", - "battery_low_template": "Template de bateria baixa", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Deixar em branco fará com que tenha mesmo o nome que o dispositivo de origem", - "battery_low_threshold": "0 irá ser usado como valor por defeito para definir o descarregada", - "battery_low_template": "Template para determinar que a bateria está baixa, deve devolver true se estiver baixa\nÉ somente necessário para níveis de bateria non-standard", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "O dispositivo ou entidade associado não existe mais para esta Nota de Bateria.", "unknown": "Error desconhecido." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/ru.json b/custom_components/battery_notes/translations/ru.json index cbbbc3c18..537bf2e94 100644 --- a/custom_components/battery_notes/translations/ru.json +++ b/custom_components/battery_notes/translations/ru.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Если вам нужна помощь с настройкой, посмотрите здесь: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Тип связи" - }, - "menu_options": { - "device": "Устройство (рекомендуется)", - "entity": "Объект" - }, - "title": "Выберите тип связи" - }, - "device": { - "data": { - "device_id": "Устройство", - "name": "Название" - }, - "data_description": { - "name": "Если оставить пустым, то название будет взято с исходного устройства" - } - }, - "entity": { - "data": { - "source_entity_id": "Объект", - "name": "Название" - }, - "data_description": { - "name": "Если оставить пустым, то название будет взято с исходного устройства" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Производитель: {manufacturer}\nМодель: {model}\nID модели: {model_id}\nРевизия: {hw_version}", @@ -44,43 +19,137 @@ "battery_low_template": "Шаблон низкого заряда батареи. В случае низкого заряда должен возвращать true.\nТребуется только для нестандартных уровней заряда батареи.", "filter_outliers": "Отфильтровать скачки заряда, уменьшая ложные срабатывания событий для устройств, которые иногда сообщают заряд батареи с ошибками" } - }, - "manual": { - "description": "Это устройство помечено в библиотеке для настройки вручную, в разных версиях используются различные типы батарей, поэтому оно не может быть настроено автоматически.\nСледующий шаг позволит вам настроить тип батареи. Пожалуйста, не отправляйте запросы на добавление настроек устройства в библиотеку.", - "title": "Ручная настройка устройства" } }, "abort": { - "already_configured": "Устройство уже настроено" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Произошла неизвестная ошибка.", - "unconfigurable_entity": "Невозможно добавить этот элемент в Battery Notes." + "unknown": "Произошла неизвестная ошибка." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Если вам нужна помощь с настройкой, посмотрите здесь: {documentation_url}", + "data": { + "association_type": "Тип связи" + }, + "menu_options": { + "device": "Устройство (рекомендуется)", + "entity": "Объект" + }, + "title": "Выберите тип связи" + }, + "device": { + "data": { + "device_id": "Устройство", + "name": "Название" + }, + "data_description": { + "name": "Если оставить пустым, то название будет взято с исходного устройства" + } + }, + "entity": { + "data": { + "source_entity_id": "Объект", + "name": "Название" + }, + "data_description": { + "name": "Если оставить пустым, то название будет взято с исходного устройства" + } + }, + "battery": { + "description": "Производитель: {manufacturer}\nМодель: {model}\nID модели: {model_id}\nРевизия: {hw_version}", + "data": { + "battery_type": "Тип батареи", + "battery_quantity": "Количество батарей", + "battery_low_threshold": "Порог низкого заряда батареи", + "battery_low_template": "Шаблон низкого заряда батареи", + "filter_outliers": "Фильтр скачков заряда" + }, + "data_description": { + "battery_low_threshold": "0 будет использовать общий порог по умолчанию", + "battery_low_template": "Шаблон низкого заряда батареи. В случае низкого заряда должен возвращать true.\nТребуется только для нестандартных уровней заряда батареи.", + "filter_outliers": "Отфильтровать скачки заряда, уменьшая ложные срабатывания событий для устройств, которые иногда сообщают заряд батареи с ошибками" + } + }, + "manual": { + "description": "Это устройство помечено в библиотеке для настройки вручную, в разных версиях используются различные типы батарей, поэтому оно не может быть настроено автоматически.\nСледующий шаг позволит вам настроить тип батареи. Пожалуйста, не отправляйте запросы на добавление настроек устройства в библиотеку.", + "title": "Ручная настройка устройства" + }, + "reconfigure": { + "description": "Производитель: {manufacturer}\nМодель: {model}\nID модели: {model_id}\nРевизия: {hw_version}", + "data": { + "name": "Название", + "battery_type": "Тип батареи", + "battery_quantity": "Количество батарей", + "battery_low_threshold": "Порог низкого заряда батареи", + "battery_low_template": "Шаблон низкого заряда батареи", + "filter_outliers": "Фильтр скачков заряда" + }, + "data_description": { + "name": "Если оставить пустым, то название будет взято с исходного устройства", + "battery_low_threshold": "0 будет использовать общий порог по умолчанию", + "battery_low_template": "Шаблон низкого заряда батареи. В случае низкого заряда должен возвращать true.\nТребуется только для нестандартных уровней заряда батареи.", + "filter_outliers": "Отфильтровать скачки заряда, уменьшая ложные срабатывания событий для устройств, которые иногда сообщают заряд батареи с ошибками" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Устройство уже настроено" + }, + "error": { + "unknown": "Произошла неизвестная ошибка.", + "unconfigurable_entity": "Невозможно добавить этот элемент в Battery Notes.", + "orphaned_battery_note": "Привязанное к этой заметке устройство или объект больше не существует." + } } }, "options": { "step": { "init": { - "description": "Производитель: {manufacturer}\nМодель: {model}\nID модели: {model_id}\nРевизия: {hw_version}", "data": { - "name": "Название", - "battery_type": "Тип батареи", - "battery_quantity": "Количество батарей", - "battery_low_threshold": "Порог низкого заряда батареи", - "battery_low_template": "Шаблон низкого заряда батареи", - "filter_outliers": "Фильтр скачков заряда" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Если оставить пустым, то название будет взято с исходного устройства", - "battery_low_threshold": "0 будет использовать общий порог по умолчанию", - "battery_low_template": "Шаблон низкого заряда батареи. В случае низкого заряда должен возвращать true.\nТребуется только для нестандартных уровней заряда батареи.", - "filter_outliers": "Отфильтровать скачки заряда, уменьшая ложные срабатывания событий для устройств, которые иногда сообщают заряд батареи с ошибками" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Привязанное к этой заметке устройство или объект больше не существует.", - "unknown": "Произошла неизвестная ошибка." + "unknown": "Unknown error occurred." } }, "entity": { @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/sk.json b/custom_components/battery_notes/translations/sk.json index 6731e21b0..4af728bd3 100644 --- a/custom_components/battery_notes/translations/sk.json +++ b/custom_components/battery_notes/translations/sk.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Ak potrebujete pomoc s konfiguráciou, pozrite sa sem: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Typ asociácie" - }, - "menu_options": { - "device": "Zariadenie (odporúčané)", - "entity": "Entita" - }, - "title": "Vyberte typ asociácie" - }, - "device": { - "data": { - "device_id": "Zariadenie", - "name": "Názov" - }, - "data_description": { - "name": "Ak ponecháte prázdne, názov sa prevezme zo zdrojového zariadenia" - } - }, - "entity": { - "data": { - "source_entity_id": "Entita", - "name": "Názov" - }, - "data_description": { - "name": "Ak ponecháte prázdne, názov sa prevezme zo zdrojovej entity" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Šablóna na určenie nízkej úrovne batérie by mala vrátiť hodnotu Pravda, ak je nízke\nPotrebné iba pri neštandardných úrovniach batérie", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", - "title": "Device manual configuration" } }, "abort": { - "already_configured": "Zariadenie je už nakonfigurované" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Vyskytla sa neznáma chyba.", - "unconfigurable_entity": "It is not possible to add this entity to Battery Notes." + "unknown": "Vyskytla sa neznáma chyba." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Ak potrebujete pomoc s konfiguráciou, pozrite sa sem: {documentation_url}", + "data": { + "association_type": "Typ asociácie" + }, + "menu_options": { + "device": "Zariadenie (odporúčané)", + "entity": "Entita" + }, + "title": "Vyberte typ asociácie" + }, + "device": { + "data": { + "device_id": "Zariadenie", + "name": "Názov" + }, + "data_description": { + "name": "Ak ponecháte prázdne, názov sa prevezme zo zdrojového zariadenia" + } + }, + "entity": { + "data": { + "source_entity_id": "Entita", + "name": "Názov" + }, + "data_description": { + "name": "Ak ponecháte prázdne, názov sa prevezme zo zdrojovej entity" + } + }, + "battery": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "battery_type": "Typ batérie", + "battery_quantity": "Množstvo batérie", + "battery_low_threshold": "Nízky prah batérie", + "battery_low_template": "Šablóna slabej batérie", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 použije globálny predvolený prah", + "battery_low_template": "Šablóna na určenie nízkej úrovne batérie by mala vrátiť hodnotu Pravda, ak je nízke\nPotrebné iba pri neštandardných úrovniach batérie", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", + "title": "Device manual configuration" + }, + "reconfigure": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "name": "Názov", + "battery_type": "Typ batérie", + "battery_quantity": "Množstvo batérie", + "battery_low_threshold": "Nízky prah batérie", + "battery_low_template": "Šablóna slabej batérie", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Ak ponecháte prázdne, názov sa prevezme zo zdrojového zariadenia", + "battery_low_threshold": "0 použije globálny predvolený prah", + "battery_low_template": "Šablóna na určenie nízkej úrovne batérie by mala vrátiť hodnotu true, ak je nízka\nPotrebné iba pri neštandardných úrovniach batérie", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Zariadenie je už nakonfigurované" + }, + "error": { + "unknown": "Vyskytla sa neznáma chyba.", + "unconfigurable_entity": "It is not possible to add this entity to Battery Notes.", + "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note." + } } }, "options": { "step": { "init": { - "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", "data": { - "name": "Názov", - "battery_type": "Typ batérie", - "battery_quantity": "Množstvo batérie", - "battery_low_threshold": "Nízky prah batérie", - "battery_low_template": "Šablóna slabej batérie", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Ak ponecháte prázdne, názov sa prevezme zo zdrojového zariadenia", - "battery_low_threshold": "0 použije globálny predvolený prah", - "battery_low_template": "Šablóna na určenie nízkej úrovne batérie by mala vrátiť hodnotu true, ak je nízka\nPotrebné iba pri neštandardných úrovniach batérie", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.", "unknown": "Vyskytla sa neznáma chyba." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/sr-Latn.json b/custom_components/battery_notes/translations/sr-Latn.json index af0b1d3c6..311f8f428 100644 --- a/custom_components/battery_notes/translations/sr-Latn.json +++ b/custom_components/battery_notes/translations/sr-Latn.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Ako vam treba pomoć oko konfiguracije, pogledajte ovde: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Vrsta povezivanja" - }, - "menu_options": { - "device": "Uređaj (preporučeno)", - "entity": "Entitet" - }, - "title": "Odaberite vrstu povezivanja" - }, - "device": { - "data": { - "device_id": "Uređaj", - "name": "Naziv" - }, - "data_description": { - "name": "Ako ostavite prazno, naziv će preuzeti sa izvornog uređaja" - } - }, - "entity": { - "data": { - "source_entity_id": "Entitet", - "name": "Naziv" - }, - "data_description": { - "name": "Ako ostavite prazno, naziv će preuzeti sa izvornog entiteta" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Šablon za određivanje da je baterija prazna, treba da vrati true ako je nizak nivo\nPotrebno samo za nestandardne nivoe baterije", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", - "title": "Device manual configuration" } }, "abort": { - "already_configured": "Uređaj je već konfigurisan" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Nepoznata greška se dogodila.", - "unconfigurable_entity": "It is not possible to add this entity to Battery Notes." + "unknown": "Nepoznata greška se dogodila." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Ako vam treba pomoć oko konfiguracije, pogledajte ovde: {documentation_url}", + "data": { + "association_type": "Vrsta povezivanja" + }, + "menu_options": { + "device": "Uređaj (preporučeno)", + "entity": "Entitet" + }, + "title": "Odaberite vrstu povezivanja" + }, + "device": { + "data": { + "device_id": "Uređaj", + "name": "Naziv" + }, + "data_description": { + "name": "Ako ostavite prazno, naziv će preuzeti sa izvornog uređaja" + } + }, + "entity": { + "data": { + "source_entity_id": "Entitet", + "name": "Naziv" + }, + "data_description": { + "name": "Ako ostavite prazno, naziv će preuzeti sa izvornog entiteta" + } + }, + "battery": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "battery_type": "Tip baterije", + "battery_quantity": "Broj baterija", + "battery_low_threshold": "Nizak prag napunjenosti baterije", + "battery_low_template": "Šablon ptazne baterije", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 će koristiti globalni podrazumevani prag", + "battery_low_template": "Šablon za određivanje da je baterija prazna, treba da vrati true ako je nizak nivo\nPotrebno samo za nestandardne nivoe baterije", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", + "title": "Device manual configuration" + }, + "reconfigure": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "name": "Naziv", + "battery_type": "Tip baterije", + "battery_quantity": "Broj baterija", + "battery_low_threshold": "Nizak prag napunjenosti baterije", + "battery_low_template": "Šablon prazne baterije", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Ako ostavite prazno, naziv će preuzeti sa izvornog uređaja", + "battery_low_threshold": "0 će koristiti globalni podrazumevani prag", + "battery_low_template": "Šablon za određivanje da je baterija prazna, treba da vrati true ako je nizak nivo\nPotrebno samo za nestandardne nivoe baterije", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Uređaj je već konfigurisan" + }, + "error": { + "unknown": "Nepoznata greška se dogodila.", + "unconfigurable_entity": "It is not possible to add this entity to Battery Notes.", + "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note." + } } }, "options": { "step": { "init": { - "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", "data": { - "name": "Naziv", - "battery_type": "Tip baterije", - "battery_quantity": "Broj baterija", - "battery_low_threshold": "Nizak prag napunjenosti baterije", - "battery_low_template": "Šablon prazne baterije", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Ako ostavite prazno, naziv će preuzeti sa izvornog uređaja", - "battery_low_threshold": "0 će koristiti globalni podrazumevani prag", - "battery_low_template": "Šablon za određivanje da je baterija prazna, treba da vrati true ako je nizak nivo\nPotrebno samo za nestandardne nivoe baterije", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.", "unknown": "Nepoznata greška se dogodila!" } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/sv-SE.json b/custom_components/battery_notes/translations/sv-SE.json index b6f59dd52..aaf54f3d8 100644 --- a/custom_components/battery_notes/translations/sv-SE.json +++ b/custom_components/battery_notes/translations/sv-SE.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Om du behöver hjälp med konfigurationen ta en titt här: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Associationstyp" - }, - "menu_options": { - "device": "Enhet (rekommenderas)", - "entity": "Entitet" - }, - "title": "Välj din associationstyp" - }, - "device": { - "data": { - "device_id": "Enhet", - "name": "Namn" - }, - "data_description": { - "name": "Genom att lämna blankt tas namnet från källenheten. " - } - }, - "entity": { - "data": { - "source_entity_id": "Entitet", - "name": "Namn" - }, - "data_description": { - "name": "Lämnas tom kommer namn tas från källentiteten" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Tillverkare: {manufacturer}\nModell: {model}\nModell ID: {model_id}\nHårdvaruversion: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Template för att bestämma om batteriet är lågt, borde returnera true om lågt.\nEndast nödvändigt för icke-standard batterinivåer.", "filter_outliers": "Filtrera bort stora batterinivåfall för att minska falska händelser på enheter som ibland rapporterar nivåer felaktigt" } - }, - "manual": { - "description": "Enheten är markerad som manuell i registret, varianter använder olika batterityper så den kan därför inte ställas in i registret.\nNästa steg gör att du kan ställa in din batterityp, men skicka inte in någon begäran om tillägg av denna batterienhet till utvecklarna.", - "title": "Manuell konfiguration av enhet" } }, "abort": { - "already_configured": "Enheten är redan konfigurerad. " + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Okänt fel inträffade. ", - "unconfigurable_entity": "Det är inte möjligt att lägga till denna enhet till Battery Notes." + "unknown": "Okänt fel inträffade." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Om du behöver hjälp med konfigurationen ta en titt här: {documentation_url}", + "data": { + "association_type": "Associationstyp" + }, + "menu_options": { + "device": "Enhet (rekommenderas)", + "entity": "Entitet" + }, + "title": "Välj din associationstyp" + }, + "device": { + "data": { + "device_id": "Enhet", + "name": "Namn" + }, + "data_description": { + "name": "Genom att lämna blankt tas namnet från källenheten. " + } + }, + "entity": { + "data": { + "source_entity_id": "Entitet", + "name": "Namn" + }, + "data_description": { + "name": "Lämnas tom kommer namn tas från källentiteten" + } + }, + "battery": { + "description": "Tillverkare: {manufacturer}\nModell: {model}\nModell ID: {model_id}\nHårdvaruversion: {hw_version}", + "data": { + "battery_type": "Batterityp", + "battery_quantity": "Antal batterier", + "battery_low_threshold": "Tröskelvärde lågt batteri", + "battery_low_template": "Batteri lågt template", + "filter_outliers": "Filtrera avvikande" + }, + "data_description": { + "battery_low_threshold": "0 kommer att använda det globala standardtröskelvärdet", + "battery_low_template": "Template för att bestämma om batteriet är lågt, borde returnera true om lågt.\nEndast nödvändigt för icke-standard batterinivåer.", + "filter_outliers": "Filtrera bort stora batterinivåfall för att minska falska händelser på enheter som ibland rapporterar nivåer felaktigt" + } + }, + "manual": { + "description": "Enheten är markerad som manuell i registret, varianter använder olika batterityper så den kan därför inte ställas in i registret.\nNästa steg gör att du kan ställa in din batterityp, men skicka inte in någon begäran om tillägg av denna batterienhet till utvecklarna.", + "title": "Manuell konfiguration av enhet" + }, + "reconfigure": { + "description": "Tillverkare: {manufacturer}\nModell: {model}\nModell ID: {model_id}\nHårdvaruversion: {hw_version}", + "data": { + "name": "Namn", + "battery_type": "Batterityp", + "battery_quantity": "Antal batterier", + "battery_low_threshold": "Batteri lågt tröskelvärde", + "battery_low_template": "Batteri lågt template", + "filter_outliers": "Filtrera avvikande värden" + }, + "data_description": { + "name": "Genom att lämna blankt tas namnet från källenheten. ", + "battery_low_threshold": "0 kommer att använda den globala standardtröskelvärdet. ", + "battery_low_template": "Template för att bestämma om batteriet är lågt. Ska returnera true om lågt. Behövs endast för icke-standard batterinivåer. ", + "filter_outliers": "Filtrera bort stora batterinivåfall för att minska falska händelser på enheter som ibland rapporterar nivåer felaktigt" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Enheten är redan konfigurerad. " + }, + "error": { + "unknown": "Okänt fel inträffade.", + "unconfigurable_entity": "Det är inte möjligt att lägga till denna enhet till Battery Notes.", + "orphaned_battery_note": "Den associerade enheten eller entiteten finns inte längre för denna Batteri \nNotering ." + } } }, "options": { "step": { "init": { - "description": "Tillverkare: {manufacturer}\nModell: {model}\nModell ID: {model_id}\nHårdvaruversion: {hw_version}", "data": { - "name": "Namn", - "battery_type": "Batterityp", - "battery_quantity": "Antal batterier", - "battery_low_threshold": "Batteri lågt tröskelvärde", - "battery_low_template": "Batteri lågt template", - "filter_outliers": "Filtrera avvikande värden" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Genom att lämna blankt tas namnet från källenheten. ", - "battery_low_threshold": "0 kommer att använda den globala standardtröskelvärdet. ", - "battery_low_template": "Template för att bestämma om batteriet är lågt. Ska returnera true om lågt. Behövs endast för icke-standard batterinivåer. ", - "filter_outliers": "Filtrera bort stora batterinivåfall för att minska falska händelser på enheter som ibland rapporterar nivåer felaktigt" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Den associerade enheten eller entiteten finns inte längre för denna Batteri \nNotering .", "unknown": "Okänt fel inträffade. " } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/tr.json b/custom_components/battery_notes/translations/tr.json index e3abfbe16..445225164 100644 --- a/custom_components/battery_notes/translations/tr.json +++ b/custom_components/battery_notes/translations/tr.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Yapılandırma ile ilgili yardıma ihtiyacınız varsa buraya bir göz atın: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "İlişkilendirme türü" - }, - "menu_options": { - "device": "Cihaz (önerilen)", - "entity": "Varlık" - }, - "title": "İlişkilendirme türünü seçin" - }, - "device": { - "data": { - "device_id": "Cihaz", - "name": "İsim" - }, - "data_description": { - "name": "Boş bırakıldığında kaynak cihazın ismi alınır" - } - }, - "entity": { - "data": { - "source_entity_id": "Varlık", - "name": "İsim" - }, - "data_description": { - "name": "Boş bırakıldığında isim kaynak varlıktan alınır" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Üretici firma: {manufacturer}\nModel: {model}\nModel Kodu: {model_id}\nDonanım sürümü: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Bir pilin zayıf olduğunu belirleyen şablon, zayıfsa true döndürmelidir\nYalnızca standart olmayan pil seviyeleri için gereklidir", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "Bu cihaz kütüphanede manuel olarak işaretlenmiştir, varyantlar farklı pil türleri kullanır, bu nedenle kütüphanede ayarlanamaz.\nBir sonraki adım pil türünüzü ayarlamanıza izin verecektir ancak lütfen bir cihaz talebi göndermeyin.", - "title": "Cihaz manuel yapılandırması" } }, "abort": { - "already_configured": "Cihaz zaten yapılandırılmış" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Bilinmeyen bir hata oluştu.", - "unconfigurable_entity": "Bu varlığı Battery Notes'a eklemek mümkün değildir." + "unknown": "Bilinmeyen bir hata oluştu." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Yapılandırma ile ilgili yardıma ihtiyacınız varsa buraya bir göz atın: {documentation_url}", + "data": { + "association_type": "İlişkilendirme türü" + }, + "menu_options": { + "device": "Cihaz (önerilen)", + "entity": "Varlık" + }, + "title": "İlişkilendirme türünü seçin" + }, + "device": { + "data": { + "device_id": "Cihaz", + "name": "İsim" + }, + "data_description": { + "name": "Boş bırakıldığında kaynak cihazın ismi alınır" + } + }, + "entity": { + "data": { + "source_entity_id": "Varlık", + "name": "İsim" + }, + "data_description": { + "name": "Boş bırakıldığında isim kaynak varlıktan alınır" + } + }, + "battery": { + "description": "Üretici firma: {manufacturer}\nModel: {model}\nModel Kodu: {model_id}\nDonanım sürümü: {hw_version}", + "data": { + "battery_type": "Pil Türü", + "battery_quantity": "Pil miktarı", + "battery_low_threshold": "Düşük pil eşiği", + "battery_low_template": "Düşük pil şablonu", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 genel varsayılan eşiği kullanacaktır", + "battery_low_template": "Bir pilin zayıf olduğunu belirleyen şablon, zayıfsa true döndürmelidir\nYalnızca standart olmayan pil seviyeleri için gereklidir", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "Bu cihaz kütüphanede manuel olarak işaretlenmiştir, varyantlar farklı pil türleri kullanır, bu nedenle kütüphanede ayarlanamaz.\nBir sonraki adım pil türünüzü ayarlamanıza izin verecektir ancak lütfen bir cihaz talebi göndermeyin.", + "title": "Cihaz manuel yapılandırması" + }, + "reconfigure": { + "description": "Üretici firma: {manufacturer}\nModel: {model}\nModel kodu: {model_id}\nDonanım sürümü: {hw_version}", + "data": { + "name": "İsim", + "battery_type": "Pil türü", + "battery_quantity": "Pil miktarı", + "battery_low_threshold": "Düşük pil eşiği", + "battery_low_template": "Düşük pil şablonu", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "Boş bırakıldığında kaynak cihazın ismi alınır", + "battery_low_threshold": "0 genel varsayılan eşiği kullanacaktır", + "battery_low_template": "Bir pilin zayıf olduğunu belirleyen şablon, zayıfsa true döndürmelidir\nYalnızca standart olmayan pil seviyeleri için gereklidir", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Cihaz zaten yapılandırılmış" + }, + "error": { + "unknown": "Bilinmeyen bir hata oluştu.", + "unconfigurable_entity": "Bu varlığı Battery Notes'a eklemek mümkün değildir.", + "orphaned_battery_note": "İlişkili cihaz veya varlık bu Battery Notes için artık mevcut değildir." + } } }, "options": { "step": { "init": { - "description": "Üretici firma: {manufacturer}\nModel: {model}\nModel kodu: {model_id}\nDonanım sürümü: {hw_version}", "data": { - "name": "İsim", - "battery_type": "Pil türü", - "battery_quantity": "Pil miktarı", - "battery_low_threshold": "Düşük pil eşiği", - "battery_low_template": "Düşük pil şablonu", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Boş bırakıldığında kaynak cihazın ismi alınır", - "battery_low_threshold": "0 genel varsayılan eşiği kullanacaktır", - "battery_low_template": "Bir pilin zayıf olduğunu belirleyen şablon, zayıfsa true döndürmelidir\nYalnızca standart olmayan pil seviyeleri için gereklidir", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "İlişkili cihaz veya varlık bu Battery Notes için artık mevcut değildir.", "unknown": "Bilinmeyen bir hata oluştu." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/uk.json b/custom_components/battery_notes/translations/uk.json index e46fe6973..064cf8905 100644 --- a/custom_components/battery_notes/translations/uk.json +++ b/custom_components/battery_notes/translations/uk.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "Якщо вам потрібна допомога з конфігурацією, подивіться тут: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Тип асоціацій" - }, - "menu_options": { - "device": "Пристрій (рекомендовано)", - "entity": "Об'єкт" - }, - "title": "Виберіть тип асоціації" - }, - "device": { - "data": { - "device_id": "Пристрій", - "name": "Назва" - }, - "data_description": { - "name": "Залишаючи незаповненим, буде взяте ім'я з основного пристрою" - } - }, - "entity": { - "data": { - "source_entity_id": "Об'єкт", - "name": "Назва" - }, - "data_description": { - "name": "Залишаючи незаповненим, буде взяте ім'я з основного об'єкта" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Виробник: {manufacturer}\nМодель: {model}\nID моделі: {model_id}\nРевізія: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "Шаблон для визначення, коли заряд батареї низький - в такому випадку має повернути \"true\"\nПотрібен тільки для нестандартних рівнів батареї", "filter_outliers": "Відфільтровуйте великі падіння рівня заряду акумулятора, зменшуючи кількість помилкових спрацьовувань на пристроях, які час від часу помилково повідомляють про рівень заряду" } - }, - "manual": { - "description": "Цей пристрій позначено у бібліотеці як налаштований вручну, різні варіанти пристрою використовують різні типи батарей, тому її не можна додати до бібліотеки.\nНаступний крок дозволить встановити тип батареї, але, будь ласка, не надсилайте запит на додання пристрою.", - "title": "Ручне налаштування пристрою" } }, "abort": { - "already_configured": "Пристрій вже налаштовано" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "Сталася невідома помилка.", - "unconfigurable_entity": "Неможливо додати цей об'єкт до Battery Notes." + "unknown": "Сталася невідома помилка." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "Якщо вам потрібна допомога з конфігурацією, подивіться тут: {documentation_url}", + "data": { + "association_type": "Тип асоціацій" + }, + "menu_options": { + "device": "Пристрій (рекомендовано)", + "entity": "Об'єкт" + }, + "title": "Виберіть тип асоціації" + }, + "device": { + "data": { + "device_id": "Пристрій", + "name": "Назва" + }, + "data_description": { + "name": "Залишаючи незаповненим, буде взяте ім'я з основного пристрою" + } + }, + "entity": { + "data": { + "source_entity_id": "Об'єкт", + "name": "Назва" + }, + "data_description": { + "name": "Залишаючи незаповненим, буде взяте ім'я з основного об'єкта" + } + }, + "battery": { + "description": "Виробник: {manufacturer}\nМодель: {model}\nID моделі: {model_id}\nРевізія: {hw_version}", + "data": { + "battery_type": "Тип батареї", + "battery_quantity": "Кількість батарей", + "battery_low_threshold": "Нижній поріг батареї", + "battery_low_template": "Шаблон низького заряду батареї", + "filter_outliers": "Відсіяти сторонні елементи" + }, + "data_description": { + "battery_low_threshold": "0 буде використовувати глобальний поріг за замовчуванням", + "battery_low_template": "Шаблон для визначення, коли заряд батареї низький - в такому випадку має повернути \"true\"\nПотрібен тільки для нестандартних рівнів батареї", + "filter_outliers": "Відфільтровуйте великі падіння рівня заряду акумулятора, зменшуючи кількість помилкових спрацьовувань на пристроях, які час від часу помилково повідомляють про рівень заряду" + } + }, + "manual": { + "description": "Цей пристрій позначено у бібліотеці як налаштований вручну, різні варіанти пристрою використовують різні типи батарей, тому її не можна додати до бібліотеки.\nНаступний крок дозволить встановити тип батареї, але, будь ласка, не надсилайте запит на додання пристрою.", + "title": "Ручне налаштування пристрою" + }, + "reconfigure": { + "description": "Виробник: {manufacturer}\nМодель: {model}\nID моделі: {model_id}\nРевізія: {hw_version}", + "data": { + "name": "Назва", + "battery_type": "Тип батареї", + "battery_quantity": "Кількість батарей", + "battery_low_threshold": "Нижній поріг батареї", + "battery_low_template": "Шаблон низького заряду батареї", + "filter_outliers": "Відсіяти сторонні елементи" + }, + "data_description": { + "name": "Залишаючи незаповненим, буде взяте ім'я з основного пристрою", + "battery_low_threshold": "0 буде використовувати глобальний поріг за замовчуванням", + "battery_low_template": "Шаблон для визначення, коли заряд батареї низький - в такому випадку має повернути \"true\"\nПотрібен тільки для нестандартних рівнів батареї", + "filter_outliers": "Відфільтровуйте великі падіння рівня заряду акумулятора, зменшуючи кількість помилкових спрацьовувань на пристроях, які час від часу помилково повідомляють про рівень заряду" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "Пристрій вже налаштовано" + }, + "error": { + "unknown": "Сталася невідома помилка.", + "unconfigurable_entity": "Неможливо додати цей об'єкт до Battery Notes.", + "orphaned_battery_note": "Пов'язаний пристрій або об'єкт більше не існує для Battery Note" + } } }, "options": { "step": { "init": { - "description": "Виробник: {manufacturer}\nМодель: {model}\nID моделі: {model_id}\nРевізія: {hw_version}", "data": { - "name": "Назва", - "battery_type": "Тип батареї", - "battery_quantity": "Кількість батарей", - "battery_low_threshold": "Нижній поріг батареї", - "battery_low_template": "Шаблон низького заряду батареї", - "filter_outliers": "Відсіяти сторонні елементи" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "Залишаючи незаповненим, буде взяте ім'я з основного пристрою", - "battery_low_threshold": "0 буде використовувати глобальний поріг за замовчуванням", - "battery_low_template": "Шаблон для визначення, коли заряд батареї низький - в такому випадку має повернути \"true\"\nПотрібен тільки для нестандартних рівнів батареї", - "filter_outliers": "Відфільтровуйте великі падіння рівня заряду акумулятора, зменшуючи кількість помилкових спрацьовувань на пристроях, які час від часу помилково повідомляють про рівень заряду" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "Пов'язаний пристрій або об'єкт більше не існує для Battery Note", "unknown": "Сталася невідома помилка." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/ur-IN.json b/custom_components/battery_notes/translations/ur-IN.json index 3e9aa588e..de5cd4b6f 100644 --- a/custom_components/battery_notes/translations/ur-IN.json +++ b/custom_components/battery_notes/translations/ur-IN.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "اگر آپ کو ترتیب میں مدد کی ضرورت ہو تو یہاں ایک نظر ڈالیں: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "Association type" - }, - "menu_options": { - "device": "Device (recommended)", - "entity": "Entity" - }, - "title": "Choose your association type" - }, - "device": { - "data": { - "device_id": "آلہ", - "name": "نام" - }, - "data_description": { - "name": "خالی چھوڑنے سے نام ماخذ آلہ سے لیا جائے گا۔" - } - }, - "entity": { - "data": { - "source_entity_id": "Entity", - "name": "نام" - }, - "data_description": { - "name": "Leaving blank will take the name from the source entity" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "بیٹری کم ہونے کا تعین کرنے کے لیے ٹیمپلیٹ، کم ہونے کی صورت میں درست ہونا چاہیے\nصرف غیر معیاری بیٹری کی سطح کے لیے ضروری ہے", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", - "title": "Device manual configuration" } }, "abort": { - "already_configured": "ڈیوائس پہلے سے ہی ترتیب شدہ ہے۔" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "نامعلوم خرابی پیش آگئی.", - "unconfigurable_entity": "It is not possible to add this entity to Battery Notes." + "unknown": "نامعلوم خرابی پیش آگئی." + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "اگر آپ کو ترتیب میں مدد کی ضرورت ہو تو یہاں ایک نظر ڈالیں: {documentation_url}", + "data": { + "association_type": "Association type" + }, + "menu_options": { + "device": "Device (recommended)", + "entity": "Entity" + }, + "title": "Choose your association type" + }, + "device": { + "data": { + "device_id": "آلہ", + "name": "نام" + }, + "data_description": { + "name": "خالی چھوڑنے سے نام ماخذ آلہ سے لیا جائے گا۔" + } + }, + "entity": { + "data": { + "source_entity_id": "Entity", + "name": "نام" + }, + "data_description": { + "name": "Leaving blank will take the name from the source entity" + } + }, + "battery": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "battery_type": "بیٹری کی قسم", + "battery_quantity": "بیٹری کی مقدار", + "battery_low_threshold": "بیٹری کی کم حد", + "battery_low_template": "کم بیٹری ٹیمپلیٹ", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "0 عالمی ڈیفالٹ حد استعمال کرے گا۔", + "battery_low_template": "بیٹری کم ہونے کا تعین کرنے کے لیے ٹیمپلیٹ، کم ہونے کی صورت میں درست ہونا چاہیے\nصرف غیر معیاری بیٹری کی سطح کے لیے ضروری ہے", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "This device is marked in the library as manual, variants use different battery types so it cannot be set in the library.\nThe next step will allow you to set your battery type but please do not submit a device request.", + "title": "Device manual configuration" + }, + "reconfigure": { + "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", + "data": { + "name": "نام", + "battery_type": "بیٹری کی قسم", + "battery_quantity": "بیٹری کی مقدار", + "battery_low_threshold": "بیٹری کی کم حد", + "battery_low_template": " کم بیٹری ٹیمپلیٹ ", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "خالی چھوڑنے سے نام ماخذ آلہ سے لیا جائے گا۔", + "battery_low_threshold": "0 عالمی ڈیفالٹ حد استعمال کرے گا۔", + "battery_low_template": "بیٹری کم ہونے کا تعین کرنے کے لیے ٹیمپلیٹ، کم ہونے کی صورت میں درست ہونا چاہیے\nصرف غیر معیاری بیٹری کی سطح کے لیے ضروری ہے", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "ڈیوائس پہلے سے ہی ترتیب شدہ ہے۔" + }, + "error": { + "unknown": "نامعلوم خرابی پیش آگئی.", + "unconfigurable_entity": "It is not possible to add this entity to Battery Notes.", + "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note." + } } }, "options": { "step": { "init": { - "description": "Manufacturer: {manufacturer}\nModel: {model}\nModel ID: {model_id}\nHardware version: {hw_version}", "data": { - "name": "نام", - "battery_type": "بیٹری کی قسم", - "battery_quantity": "بیٹری کی مقدار", - "battery_low_threshold": "بیٹری کی کم حد", - "battery_low_template": " کم بیٹری ٹیمپلیٹ ", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "خالی چھوڑنے سے نام ماخذ آلہ سے لیا جائے گا۔", - "battery_low_threshold": "0 عالمی ڈیفالٹ حد استعمال کرے گا۔", - "battery_low_template": "بیٹری کم ہونے کا تعین کرنے کے لیے ٹیمپلیٹ، کم ہونے کی صورت میں درست ہونا چاہیے\nصرف غیر معیاری بیٹری کی سطح کے لیے ضروری ہے", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "The associated device or entity no longer exists for this Battery Note.", "unknown": "نامعلوم خرابی پیش آگئی." } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/zh-Hans.json b/custom_components/battery_notes/translations/zh-Hans.json index da17b3441..de1cdb1ea 100644 --- a/custom_components/battery_notes/translations/zh-Hans.json +++ b/custom_components/battery_notes/translations/zh-Hans.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "若在设定过程中有任何疑问,请参考: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "关联类型" - }, - "menu_options": { - "device": "装置(推荐)", - "entity": "实体" - }, - "title": "选择关联类型" - }, - "device": { - "data": { - "device_id": "装置", - "name": "名称" - }, - "data_description": { - "name": "若留空,将使用来源装置的名称" - } - }, - "entity": { - "data": { - "source_entity_id": "实体", - "name": "名称" - }, - "data_description": { - "name": "若留空,将使用来源实体的名称" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "制造商:{manufacturer}\n型号:{model}\n型号 ID:{model_id}\n硬件版本:{hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "判断电池是否低电量的模板,若为低电量应回传 true\n仅在电池电量非标准时需要", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "此装置在数据库中被标记为手动设定,不同变体使用不同电池类型,因此无法在数据库中设定。\n下一步将让你设定电池类型,但请勿提交装置请求。", - "title": "装置手动设定" } }, "abort": { - "already_configured": "装置已设定过" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "发生未知错误。", - "unconfigurable_entity": "无法将此实体新增至 Battery Notes。" + "unknown": "发生未知错误。" + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "若在设定过程中有任何疑问,请参考: {documentation_url}", + "data": { + "association_type": "关联类型" + }, + "menu_options": { + "device": "装置(推荐)", + "entity": "实体" + }, + "title": "选择关联类型" + }, + "device": { + "data": { + "device_id": "装置", + "name": "名称" + }, + "data_description": { + "name": "若留空,将使用来源装置的名称" + } + }, + "entity": { + "data": { + "source_entity_id": "实体", + "name": "名称" + }, + "data_description": { + "name": "若留空,将使用来源实体的名称" + } + }, + "battery": { + "description": "制造商:{manufacturer}\n型号:{model}\n型号 ID:{model_id}\n硬件版本:{hw_version}", + "data": { + "battery_type": "电池类型", + "battery_quantity": "电池数量", + "battery_low_threshold": "低电量阈值", + "battery_low_template": "低电量模板", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "若为零,将使用全域默认阈值", + "battery_low_template": "判断电池是否低电量的模板,若为低电量应回传 true\n仅在电池电量非标准时需要", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "此装置在数据库中被标记为手动设定,不同变体使用不同电池类型,因此无法在数据库中设定。\n下一步将让你设定电池类型,但请勿提交装置请求。", + "title": "装置手动设定" + }, + "reconfigure": { + "description": "制造商:{manufacturer}\n型号:{model}\n型号 ID:{model_id}\n硬件版本:{hw_version}", + "data": { + "name": "名称", + "battery_type": "电池类型", + "battery_quantity": "电池数量", + "battery_low_threshold": "低电量阈值", + "battery_low_template": "低电量模板", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "若留空,将使用来源装置的名称", + "battery_low_threshold": "若为零,将使用全域默认阈值", + "battery_low_template": "判断电池是否低电量的模板,若为低电量应回传 true\n仅在电池电量非标准时需要", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "装置已设定过" + }, + "error": { + "unknown": "发生未知错误。", + "unconfigurable_entity": "无法将此实体新增至 Battery Notes。", + "orphaned_battery_note": "此 Battery Note 的关联装置或实体已不存在。" + } } }, "options": { "step": { "init": { - "description": "制造商:{manufacturer}\n型号:{model}\n型号 ID:{model_id}\n硬件版本:{hw_version}", "data": { - "name": "名称", - "battery_type": "电池类型", - "battery_quantity": "电池数量", - "battery_low_threshold": "低电量阈值", - "battery_low_template": "低电量模板", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "若留空,将使用来源装置的名称", - "battery_low_threshold": "若为零,将使用全域默认阈值", - "battery_low_template": "判断电池是否低电量的模板,若为低电量应回传 true\n仅在电池电量非标准时需要", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "此 Battery Note 的关联装置或实体已不存在。", "unknown": "发生未知错误。" } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/custom_components/battery_notes/translations/zh-Hant.json b/custom_components/battery_notes/translations/zh-Hant.json index b0930d902..3b926fa54 100644 --- a/custom_components/battery_notes/translations/zh-Hant.json +++ b/custom_components/battery_notes/translations/zh-Hant.json @@ -2,33 +2,8 @@ "config": { "step": { "user": { - "description": "若在設定過程中有任何疑問,請參考: https://andrew-codechimp.github.io/HA-Battery-Notes/", - "data": { - "association_type": "關聯類型" - }, - "menu_options": { - "device": "裝置(建議)", - "entity": "實體" - }, - "title": "選擇關聯類型" - }, - "device": { - "data": { - "device_id": "裝置", - "name": "名稱" - }, - "data_description": { - "name": "若留空,將使用來源裝置的名稱" - } - }, - "entity": { - "data": { - "source_entity_id": "實體", - "name": "名稱" - }, - "data_description": { - "name": "若留空,將使用來源實體的名稱" - } + "description": "New Battery Notes will be discovered if they are in the library.\nTo add new Battery Notes manually go into the integration page, select Add Battery Note and follow the instructions.\nIf you need help with the configuration have a look here: {documentation_url}", + "title": "Setup Battery Notes" }, "battery": { "description": "製造商:{manufacturer}\n型號:{model}\n型號 ID:{model_id}\n硬體版本:{hw_version}", @@ -44,42 +19,136 @@ "battery_low_template": "判斷電池是否低電量的模板,若為低電量應回傳 true\n僅在電池電量非標準時需要", "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" } - }, - "manual": { - "description": "此裝置在資料庫中被標記為手動設定,不同變體使用不同電池類型,因此無法在資料庫中設定。\n下一步將讓你設定電池類型,但請勿提交裝置請求。", - "title": "裝置手動設定" } }, "abort": { - "already_configured": "裝置已設定過" + "already_configured": "Integration is already configured", + "integration_not_added": "Integration not added, please add the integration first", + "created_sub_entry": "Battery Note created successfully" }, "error": { - "unknown": "發生未知錯誤。", - "unconfigurable_entity": "無法將此實體新增至 Battery Notes。" + "unknown": "發生未知錯誤。" + } + }, + "config_subentries": { + "battery_note": { + "initiate_flow": { + "user": "Add battery note" + }, + "entry_type": "Battery note", + "step": { + "user": { + "description": "若在設定過程中有任何疑問,請參考: {documentation_url}", + "data": { + "association_type": "關聯類型" + }, + "menu_options": { + "device": "裝置(建議)", + "entity": "實體" + }, + "title": "選擇關聯類型" + }, + "device": { + "data": { + "device_id": "裝置", + "name": "名稱" + }, + "data_description": { + "name": "若留空,將使用來源裝置的名稱" + } + }, + "entity": { + "data": { + "source_entity_id": "實體", + "name": "名稱" + }, + "data_description": { + "name": "若留空,將使用來源實體的名稱" + } + }, + "battery": { + "description": "製造商:{manufacturer}\n型號:{model}\n型號 ID:{model_id}\n硬體版本:{hw_version}", + "data": { + "battery_type": "電池類型", + "battery_quantity": "電池數量", + "battery_low_threshold": "低電量閾值", + "battery_low_template": "低電量模板", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "battery_low_threshold": "若為零,將使用全域預設閾值", + "battery_low_template": "判斷電池是否低電量的模板,若為低電量應回傳 true\n僅在電池電量非標準時需要", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + }, + "manual": { + "description": "此裝置在資料庫中被標記為手動設定,不同變體使用不同電池類型,因此無法在資料庫中設定。\n下一步將讓你設定電池類型,但請勿提交裝置請求。", + "title": "裝置手動設定" + }, + "reconfigure": { + "description": "製造商:{manufacturer}\n型號:{model}\n型號 ID:{model_id}\n硬體版本:{hw_version}", + "data": { + "name": "名稱", + "battery_type": "電池類型", + "battery_quantity": "電池數量", + "battery_low_threshold": "低電量閾值", + "battery_low_template": "低電量模板", + "filter_outliers": "Filter outliers" + }, + "data_description": { + "name": "若留空,將使用來源裝置的名稱", + "battery_low_threshold": "若為零,將使用全域預設閾值", + "battery_low_template": "判斷電池是否低電量的模板,若為低電量應回傳 true\n僅在電池電量非標準時需要", + "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + } + } + }, + "abort": { + "reconfigure_successful": "Re-configuration was successful", + "already_configured": "裝置已設定過" + }, + "error": { + "unknown": "發生未知錯誤。", + "unconfigurable_entity": "無法將此實體新增至 Battery Notes。", + "orphaned_battery_note": "此 Battery Note 的關聯裝置或實體已不存在。" + } } }, "options": { "step": { "init": { - "description": "製造商:{manufacturer}\n型號:{model}\n型號 ID:{model_id}\n硬體版本:{hw_version}", "data": { - "name": "名稱", - "battery_type": "電池類型", - "battery_quantity": "電池數量", - "battery_low_threshold": "低電量閾值", - "battery_low_template": "低電量模板", - "filter_outliers": "Filter outliers" + "show_all_devices": "Show all devices", + "hide_battery": "Hide battery", + "round_battery": "Round battery", + "default_battery_low_threshold": "Default battery low threshold", + "battery_increase_threshold": "Battery increase threshold" }, "data_description": { - "name": "若留空,將使用來源裝置的名稱", - "battery_low_threshold": "若為零,將使用全域預設閾值", - "battery_low_template": "判斷電池是否低電量的模板,若為低電量應回傳 true\n僅在電池電量非標準時需要", - "filter_outliers": "Filter out large battery level drops, reducing falsely firing events on devices that erroneously report levels occasionally" + "show_all_devices": "Show all devices in the device dropdown, otherwise only those with batteries will be shown.", + "hide_battery": "Hide the standard battery when adding Battery+.", + "round_battery": "Round Battery+ to whole percentages.", + "default_battery_low_threshold": "The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired if the battery is below this threshold, can be overriden per device in device configuration.", + "battery_increase_threshold": "The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level." + }, + "sections": { + "advanced_settings": { + "name": "Advanced settings", + "data": { + "enable_autodiscovery": "Auto discovery", + "enable_replaced": "Enable battery replaced", + "user_library": "User library" + }, + "data_description": { + "enable_autodiscovery": "Auto discovery of devices that are in the library.", + "enable_replaced": "Enable the battery replaced button on each battery note.", + "user_library": "If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in `config/.storage/battery_notes`." + } + } } } }, "error": { - "orphaned_battery_note": "此 Battery Note 的關聯裝置或實體已不存在。", "unknown": "發生未知錯誤。" } }, @@ -199,6 +268,15 @@ } } } + }, + "deprecated_yaml": { + "title": "The {integration_title} YAML configuration is being removed", + "description": "Configuring {integration_title} using YAML is being removed.\n\nYour existing YAML configuration has been imported into the UI automatically.\n\nRemove the `{domain}` configuration from your configuration.yaml file and restart Home Assistant to fix this issue." + } + }, + "exceptions": { + "not_configured_in_battery_notes": { + "message": "{source} is not configured in Battery Notes." } } } \ No newline at end of file diff --git a/docs/assets/screenshot-device-info.png b/docs/assets/screenshot-device-info.png index 9c8089447..dc50e6cb2 100644 Binary files a/docs/assets/screenshot-device-info.png and b/docs/assets/screenshot-device-info.png differ diff --git a/docs/blueprints/battery_notes_battery_not_reported.yaml b/docs/blueprints/battery_notes_battery_not_reported.yaml index 0bcc716b6..a201ac007 100755 --- a/docs/blueprints/battery_notes_battery_not_reported.yaml +++ b/docs/blueprints/battery_notes_battery_not_reported.yaml @@ -18,8 +18,6 @@ blueprint: selector: device: multiple: true - entity: - - integration: battery_notes include_devices_enabled: name: Enable Include Devices Filter description: Only trigger for specific devices when enabled. @@ -33,8 +31,6 @@ blueprint: selector: device: multiple: true - entity: - - integration: battery_notes additional_conditions: name: Additional conditions description: | diff --git a/docs/blueprints/battery_notes_battery_threshold.yaml b/docs/blueprints/battery_notes_battery_threshold.yaml index 4c7417479..12556a90d 100644 --- a/docs/blueprints/battery_notes_battery_threshold.yaml +++ b/docs/blueprints/battery_notes_battery_threshold.yaml @@ -23,7 +23,7 @@ blueprint: description: Do not trigger for specific devices when enabled. default: true selector: - boolean: + boolean: excluded_devices: name: Devices to exclude (Optional) description: When enabled above, these devices will not trigger this automation. @@ -31,8 +31,6 @@ blueprint: selector: device: multiple: true - entity: - - integration: battery_notes include_devices_enabled: name: Enable Include Devices Filter description: Only trigger for specific devices when enabled. @@ -46,8 +44,6 @@ blueprint: selector: device: multiple: true - entity: - - integration: battery_notes additional_conditions: name: Additional conditions description: | diff --git a/docs/configuration.md b/docs/configuration.md index 91ba5857a..018b1b2dd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,30 +1,17 @@ # Configuration -You can add these options to change the default behaviour of Battery Notes by adding them to your Home Assistant configuration.yaml under the battery_notes: property, like so: - -``` -battery_notes: - enable_autodiscovery: true - show_all_devices: false - enable_replaced: true - default_battery_low_threshold: 10 - battery_increase_threshold: 25 - hide_battery: false - round_battery: false -``` - -A restart of Home Assistant is required for the changed to take effect. - -Name | Type | Requirement | Default | Description | --- | -- | -- | -- | -- | -enable_autodiscovery | Boolean | Optional | True | If set to true will automatically match devices against the library and create a setup flow within the integrations page. | -show_all_devices | Boolean | Optional | False | If set to true will show all devices in the manual add dropdown, rather than just those with batteries. | -enable_replaced | Boolean | Optional | True | If set to false new devices added to battery notes will have the battery replaced sensor and button disabled. Any devices you have previously added to Battery Notes you will have to disable these sensors manually, which also means you can enable specific sensors of important ones you want to track. | -default_battery_low_threshold | Int | Optional | 10 | The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired, can be overriden per device in device configuration. | -battery_increase_threshold | Int | Optional | 25 | The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level. | -hide_battery | Boolean | Optional | False | Hide the standard battery when adding Battery+. This will not effect existing dashboards, automations etc.| -round_battery | Boolean | Optional | False | Round battery+ to whole percentages.| -user_library | String | Optional | | If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in the same folder (config/.storage/battery_notes). Only really used for dev purposes. | +Global configuration settings can be changed by selecting the configuration cog on the main Battery Notes service. + +| Name | Description | +| ---------------- | ---------------------------------------------------------------------------------------- | +| Show all devices | Will show all devices in the manual add dropdown, rather than just those with batteries. | +| Hide battery | Hide the standard battery when adding Battery+. This will not effect existing dashboards, automations etc.| +| Round battery | Round battery+ to whole percentages.| +| Default battery low threshold | The default threshold where a devices battery_low entity is set to true and the battery_notes_battery_threshold event is fired, can be overriden per device in device configuration. | +|Battery increase threshold | The threshold where the battery_notes_battery_increased event is fired, use this event for battery replaced automations. The threshold is the difference in increase between previous and current battery level. | +| Auto discovery | Will automatically match devices against the library and create a setup flow within the integrations page, this occurs at instegration startup and repeats every 24 hours. | +| Enable battery replaced | New battery notes will have a battery replaced sensor and butoon. If disabled new devices added to battery notes will have the battery replaced sensor and button disabled. Any battery notes you have previously added you will have to disable/re-enable these sensors manually, which also means you can enable specific sensors of important ones you want to track. | +| User library | If specified then a user library file will be searched prior to the main library, the user library must be in the same format as the library and placed in the same folder (config/.storage/battery_notes). Only really used for dev purposes. | # Debug Logging diff --git a/docs/entities.md b/docs/entities.md index e3a2d84df..ed0c93a62 100644 --- a/docs/entities.md +++ b/docs/entities.md @@ -22,7 +22,7 @@ See how to use this entity in the [community contributions](./community.md) | `battery_last_reported` | `datetime` | The datetime when the battery level was last reported | | `battery_last_reported_level` | `float` | The level when the battery was last reported | | `device_id` | `string` | The device_id of the device | -| `device_name` | `string` | The name of the device | +| `device_name` | `string` | The name of the device, if you have renamed the battery note it will use this name | | `source_entity_id` | `string` | The entity_id the battery note is associated with | ### Adding a battery percentage @@ -86,5 +86,5 @@ If you have specified a manual template then this will be created, reflecting th | `battery_type_and_quantity` | `string` | The type of batteries with the quantity if more than 1 | | `battery_last_replaced` | `string` | The date and time the battery was last replaced | | `device_id` | `string` | The device_id of the device | -| `device_name` | `string` | The name of the device | +| `device_name` | `string` | The name of the device, if you have renamed the battery note it will use this name | | `source_entity_id` | `string` | The entity_id the battery note is associated with | diff --git a/docs/events.md b/docs/events.md index 62ee80205..368ce7f0d 100644 --- a/docs/events.md +++ b/docs/events.md @@ -17,7 +17,7 @@ You can use this to send notifications in your preferred method. An example aut |-----------|------|-------------| | `device_id` | `string` | The device id of the device. | | `source_entity_id` | `string` | The entity id of the sensor associated with the battery note. | -| `device_name` | `string` | The device name (or associated sensor name if no device). | +| `device_name` | `string` | The device name (or associated sensor name if no device), if you have renamed the battery note it will use this name. | | `battery_low` | `bool` | Returns true if the battery has gone below the threshold, false when the battery has returned above the threshold. **Your automations will almost certainly want to examine this value and set/clear notifications or other indicators.** | | `battery_low_threshold` | `string` | Battery low threshold (or global if 0). | | `battery_type_and_quantity` | `string` | Battery type & quantity. | @@ -93,7 +93,7 @@ An example automation below shows how to update the battery_replaced. |-----------|------|-------------| | `device_id` | `string` | The device id of the device. | | `source_entity_id` | `string` | The entity id of the sensor associated with the battery note. | -| `device_name` | `string` | The device name (or associated sensor name if no device). | +| `device_name` | `string` | The device name (or associated sensor name if no device), if you have renamed the battery note it will use this name. | | `battery_low` | `bool` | Returns true if the battery has gone below the threshold, false when the battery has returned above the threshold. | | `battery_low_threshold` | `string` | Battery low threshold (or global if 0). | | `battery_type_and_quantity` | `string` | Battery type & quantity. | @@ -134,7 +134,7 @@ The action can raise multiple events quickly so when using with an automation it |-----------|------|-------------| | `device_id` | `string` | The device id of the device. | | `source_entity_id` | `string` | The entity id of the sensor associated with the battery note. | -| `device_name` | `string` | The device name (or associated sensor name if no device). | +| `device_name` | `string` | The device name (or associated sensor name if no device), if you have renamed the battery note it will use this name. | | `battery_type_and_quantity` | `string` | Battery type & quantity. | | `battery_type` | `string` | Battery type. | | `battery_quantity` | `int` | Battery quantity. | @@ -183,7 +183,7 @@ This can be useful for adding batteries to a shopping list or inventory system. |-----------|------|-------------| | `device_id` | `string` | The device id of the device. | | `source_entity_id` | `string` | The entity id of the sensor associated with the battery note. | -| `device_name` | `string` | The device name (or associated sensor name if no device). | +| `device_name` | `string` | The device name (or associated sensor name if no device), if you have renamed the battery note it will use this name. | | `battery_type_and_quantity` | `string` | Battery type & quantity. | | `battery_type` | `string` | Battery type. | | `battery_quantity` | `int` | Battery quantity. | diff --git a/docs/faq.md b/docs/faq.md index 9d18039ee..d5e2ab26c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -23,30 +23,15 @@ Go into Settings -> Integrations -> Battery Notes and click Configure on the dev ## Why am I only able to see some of my devices when adding manually? -By default Battery Notes filters the device list to only devices with a battery, if you want to add a battery note to a random device then you can disable this filtering by adding the following configuration to your `configuration.yaml` and restart Home Assistant to see all devices. - -``` -battery_notes: - show_all_devices: True -``` +By default Battery Notes filters the device list to only devices with a battery, if you want to add a battery note to a random device then you can disable this filtering by turning on the Show all devices configuration setting. ## I only want to add notes to a few devices, can I disable auto discovery? -If you want to disable this functionality you can add the following to your `configuration.yaml`, after a restart of Home Assistant you will not see discovered battery notes. - -``` -battery_notes: - enable_autodiscovery: False -``` +If you want to disable this functionality you can turn off auto discovery in the configuration settings ## I don't want to track battery replacement, can I disable this? -Yes, you can add the following to your `configuration.yaml`, after a restart of Home Assistant _new_ devices added to battery notes will have the battery replaced sensor and button disabled. Any devices you have previously added to Battery Notes you will have to disable/enable these sensors manually, which also means you can just enable specific sensors of important ones you want to track. - -``` -battery_notes: - enable_replaced: False -``` +Yes, you can turn off enable replaced within configuration settings. _new_ devices added to battery notes will have the battery replaced sensor and button disabled. Any devices you have previously added to Battery Notes you will have to disable/enable these sensors manually, which also means you can just enable specific sensors of important ones you want to track. ## My device doesn't show a Battery+ sensor @@ -94,10 +79,9 @@ HACS will now show updates available for pre-releases if there are any ## How do I uninstall Battery Notes Within Home Assistant go to Settings -> Integrations -> Battery Notes -For each Battery Note click on the three dots and select Delete +Click on the three dots for the top Battery Notes service and select Delete Go to HACS from your sidebar Click on the three dots next to Battery Notes and select Remove -Edit your configuration.yaml file and remove the battery_notes section and any options indented directly under it Restart Home Assistant ## How can I show my support? diff --git a/docs/index.md b/docs/index.md index 991742ec2..063c52b75 100644 --- a/docs/index.md +++ b/docs/index.md @@ -34,8 +34,7 @@ Once you have [installed the integration](https://github.com/andrew-codechimp/HA ## To add a battery note manually -- Go to Settings/Integrations and click Add Integration. -- Select Battery Notes. +- Go to Settings/Integrations/Battery Notes and click Add Battery Note. - Choose Device or Entity and click next. Device will automatically detect a battery and will work for most common devices, you should use entity if your device has multiple batteries or when an entity does not have a device. - Choose your device or entity from the drop down and click next. - Enter the battery type and quantity and optionally a battery low threshold and click submit. diff --git a/docs/library.md b/docs/library.md index 89c4fb16b..401a9866c 100644 --- a/docs/library.md +++ b/docs/library.md @@ -32,7 +32,7 @@ For the example image below, your JSON entry will look like this: "manufacturer": "Philips", "model": "Hue motion sensor", "model_id": "9290012607", < Optional, only add it if your device shows it. - "hw_version": "Some specific hardware detail", < Optional, only add it if your device shows it. + "hw_version": "1", < Optional, only add it if your device shows it. "battery_type": "AAA", "battery_quantity": 2, < Only use if more than 1 battery "model_match_method": "startswith|endswith|contains" < Only use if you are creating a model with unique identifier (ex. trailing serial numbers) diff --git a/hacs.json b/hacs.json index c7dc0d44c..642bf8970 100644 --- a/hacs.json +++ b/hacs.json @@ -2,6 +2,6 @@ "name": "Battery Notes", "filename": "battery_notes.zip", "hide_default_branch": true, - "homeassistant": "2025.4.0", + "homeassistant": "2025.9.0", "zip_release": true } diff --git a/pyproject.toml b/pyproject.toml index a1aa56add..9b7b90698 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,9 +26,9 @@ repository = "https://github.com/andrew-codechimp/HA-Battery-Notes" Changelog = "https://github.com/andrew-codechimp/HA-Battery-Notes/releases" [dependency-groups] -dev = [ +dev = [ "colorlog", - "homeassistant==2025.4.0", + "homeassistant==2025.9.0", "ruff", ] @@ -82,36 +82,27 @@ ignore = [ "W191", "RUF012", # Broken rule in some cases + # GitHub actions + "INP001", # File is part of an implicit namespace package + + # Project preference + "ERA001", # Found commented-out code "RUF001", # String contains ambiguous character "S101", # Use of assert detected "TC001", # Move application import into type checking + "TC006", # Add quotes to type expression in typing.cast() # TODO: Temporary ignores, to review "ANN001", # Missing type annotation for function argument "ANN201", # Missing return type annotation for public function "ANN202", # Missing return type annotation for private function "ANN204", # Missing return type annotation for special method - "ARG001", # Unused function argument - "ARG002", # Unused method argument - "ARG004", # Unused static method argument - "COM818", # Trailing comma on bare tuple prohibited - "D204", # 1 blank line required after class docstring "DTZ005", # The use of `datetime.datetime.now()` without `tz` argument is not allowed "EM101", # Exception must not use a string literal, assign to variable first "EM102", # Exception must not use an f-string literal, assign to variable first - "ERA001", # Found commented-out code "FBT001", # Boolean-typed positional argument in function definition "FBT002", # Boolean-typed positional argument in function definition - "G004", # Logging statement uses f-string - "I001", # Import block is un-sorted or un-formatted - "INP001", # File is part of an implicit namespace package - "PGH003", # Use specific rule codes when ignoring type issues - "PLR0912", # Too many branches - "PLR0913", # Too many arguments in function definition - "PLR0915", # Too many statements "PLR2004", # Magic value used in comparison - "PLR5501", # Use `elif` instead of `else` then `if` - "PLW2901", # `for` loop variable overwritten by assignment target "PTH103", # `os.makedirs()` should be replaced by `Path.mkdir()` "PTH104", # `os.rename()` should be replaced by `Path.rename()` "PTH118", # `os.path.join()` should be replaced by `Path` with `/` operator @@ -120,18 +111,12 @@ ignore = [ "PYI041", # Use `float` instead of `int | float` "RET504", # Unnecessary assignment before `return` statement "RET505", # Unnecessary `else` after `return` statement - "RUF059", # Unpacked variable is never used "SIM102", # Use a single `if`-statement instead of nested `if`-statements - "SIM103", # Return the condition directly - "SLF001", # Private member accessed "TC003", # Move standard library import into a type-checking block - "TC006", # Move third-party import into a type-checking block "TRY003", # Avoid specifying long messages outside the exception class "TRY004", # Prefer `TypeError` exception for invalid type "TRY300", # Consider moving this statement to an `else` block "TRY400", # Use `logging.exception` instead of `logging.error` - "UP039", # Unnecessary parentheses after class name - "W292", # No newline at end of file ] diff --git a/uv.lock b/uv.lock index 7597b18e6..efa7a9e33 100644 --- a/uv.lock +++ b/uv.lock @@ -4,31 +4,30 @@ requires-python = "==3.13.2" [[package]] name = "acme" -version = "3.2.0" +version = "4.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "josepy" }, { name = "pyopenssl" }, { name = "pyrfc3339" }, - { name = "pytz" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/6a/c94be0e8ee157f3c7721844863589c627c40bfa10d8838faeb6b28b59bb2/acme-3.2.0.tar.gz", hash = "sha256:e11d0ccf43ec19244ada40df1dc4ca49c9ce407749f3771d2cefe0674e206d84", size = 92875, upload-time = "2025-02-11T21:35:57.947Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/df/d006c4920fd04b843c21698bd038968cb9caa3315608f55abde0f8e4ad6b/acme-4.2.0.tar.gz", hash = "sha256:0df68c0e1acb3824a2100013f8cd51bda2e1a56aa23447449d14c942959f0c41", size = 96820, upload-time = "2025-08-05T19:19:08.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/1c/da759f277879f5b8aa0b6355689e091224b8af500c6983a1c8fb66bad5b0/acme-3.2.0-py3-none-any.whl", hash = "sha256:201b118d12426f746d936efc61706d30dc2f9e2635aebab0c86ec7f80eca5f30", size = 97444, upload-time = "2025-02-11T21:35:25.465Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/9ff889b5d762616bf92ecbeb1ab93faddfd7bf6068146340359e9a6beb43/acme-4.2.0-py3-none-any.whl", hash = "sha256:6292011bbfa5f966521b2fb9469982c24ff4c58e240985f14564ccf35372e79a", size = 101573, upload-time = "2025-08-05T19:18:45.266Z" }, ] [[package]] name = "aiodns" -version = "3.2.0" +version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycares" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/84/41a6a2765abc124563f5380e76b9b24118977729e25a84112f8dfb2b33dc/aiodns-3.2.0.tar.gz", hash = "sha256:62869b23409349c21b072883ec8998316b234c9a9e36675756e8e317e8768f72", size = 7823, upload-time = "2024-03-31T11:27:30.639Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/0a/163e5260cecc12de6abc259d158d9da3b8ec062ab863107dcdb1166cdcef/aiodns-3.5.0.tar.gz", hash = "sha256:11264edbab51896ecf546c18eb0dd56dff0428c6aa6d2cd87e643e07300eb310", size = 14380, upload-time = "2025-06-13T16:21:53.595Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/14/13c65b1bd59f7e707e0cc0964fbab45c003f90292ed267d159eeeeaa2224/aiodns-3.2.0-py3-none-any.whl", hash = "sha256:e443c0c27b07da3174a109fd9e736d69058d808f144d3c9d56dbd1776964c5f5", size = 5735, upload-time = "2024-03-31T11:27:28.615Z" }, + { url = "https://files.pythonhosted.org/packages/f6/2c/711076e5f5d0707b8ec55a233c8bfb193e0981a800cd1b3b123e8ff61ca1/aiodns-3.5.0-py3-none-any.whl", hash = "sha256:6d0404f7d5215849233f6ee44854f2bb2481adf71b336b2279016ea5990ca5c5", size = 8068, upload-time = "2025-06-13T16:21:52.45Z" }, ] [[package]] @@ -42,22 +41,21 @@ wheels = [ [[package]] name = "aiohasupervisor" -version = "0.3.0" +version = "0.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, { name = "mashumaro" }, { name = "orjson" }, - { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/23/eceea174c1d827adea8a8b23f1428454157288fd58e6a9231e8861a45383/aiohasupervisor-0.3.0.tar.gz", hash = "sha256:91bf0b051f28582196f900a31c9bcbebec6de9e3ed1a32a2947a892c04748ce2", size = 40542, upload-time = "2025-02-05T14:41:08.946Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/13/e9c818c4be157db6383d6e0f5c56df4764553c241bc566cd819f42bd398c/aiohasupervisor-0.3.2.tar.gz", hash = "sha256:eb291b600cc5cf05072e4bd16df5655cfaea3b9f3b964844896b38230e529a7c", size = 42599, upload-time = "2025-08-26T14:47:47.357Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/09/98c83e4a20ae951d49720caeb6daf784293498fab0450af5b2236ca0c079/aiohasupervisor-0.3.0-py3-none-any.whl", hash = "sha256:f85b45c80ee24b381523e5a84a39f962f25e72c90026a3dcef2becea1d7f5501", size = 38550, upload-time = "2025-02-05T14:41:06.838Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e4/6f62ce34142558cb748c20976dfd8696b8bb4ed71653d43795f3de26a07d/aiohasupervisor-0.3.2-py3-none-any.whl", hash = "sha256:93599f698e7daf238e8040053d455104bac149f9e59fda757c6378708fbd1629", size = 39220, upload-time = "2025-08-26T14:47:46.262Z" }, ] [[package]] name = "aiohttp" -version = "3.11.16" +version = "3.12.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -68,24 +66,25 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/d9/1c4721d143e14af753f2bf5e3b681883e1f24b592c0482df6fa6e33597fa/aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8", size = 7676826, upload-time = "2025-04-02T02:17:44.74Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/52/7c712b2d9fb4d5e5fd6d12f9ab76e52baddfee71e3c8203ca7a7559d7f51/aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489", size = 698005, upload-time = "2025-04-02T02:16:37.923Z" }, - { url = "https://files.pythonhosted.org/packages/51/3e/61057814f7247666d43ac538abcd6335b022869ade2602dab9bf33f607d2/aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50", size = 461106, upload-time = "2025-04-02T02:16:39.961Z" }, - { url = "https://files.pythonhosted.org/packages/4f/85/6b79fb0ea6e913d596d5b949edc2402b20803f51b1a59e1bbc5bb7ba7569/aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133", size = 453394, upload-time = "2025-04-02T02:16:41.562Z" }, - { url = "https://files.pythonhosted.org/packages/4b/04/e1bb3fcfbd2c26753932c759593a32299aff8625eaa0bf8ff7d9c0c34a36/aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0", size = 1666643, upload-time = "2025-04-02T02:16:43.62Z" }, - { url = "https://files.pythonhosted.org/packages/0e/27/97bc0fdd1f439b8f060beb3ba8fb47b908dc170280090801158381ad7942/aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca", size = 1721948, upload-time = "2025-04-02T02:16:45.617Z" }, - { url = "https://files.pythonhosted.org/packages/2c/4f/bc4c5119e75c05ef15c5670ef1563bbe25d4ed4893b76c57b0184d815e8b/aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d", size = 1774454, upload-time = "2025-04-02T02:16:48.562Z" }, - { url = "https://files.pythonhosted.org/packages/73/5b/54b42b2150bb26fdf795464aa55ceb1a49c85f84e98e6896d211eabc6670/aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb", size = 1677785, upload-time = "2025-04-02T02:16:50.367Z" }, - { url = "https://files.pythonhosted.org/packages/10/ee/a0fe68916d3f82eae199b8535624cf07a9c0a0958c7a76e56dd21140487a/aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4", size = 1608456, upload-time = "2025-04-02T02:16:52.158Z" }, - { url = "https://files.pythonhosted.org/packages/8b/48/83afd779242b7cf7e1ceed2ff624a86d3221e17798061cf9a79e0b246077/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7", size = 1622424, upload-time = "2025-04-02T02:16:54.386Z" }, - { url = "https://files.pythonhosted.org/packages/6f/27/452f1d5fca1f516f9f731539b7f5faa9e9d3bf8a3a6c3cd7c4b031f20cbd/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd", size = 1660943, upload-time = "2025-04-02T02:16:56.887Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e1/5c7d63143b8d00c83b958b9e78e7048c4a69903c760c1e329bf02bac57a1/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f", size = 1622797, upload-time = "2025-04-02T02:16:58.676Z" }, - { url = "https://files.pythonhosted.org/packages/46/9e/2ac29cca2746ee8e449e73cd2fcb3d454467393ec03a269d50e49af743f1/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd", size = 1687162, upload-time = "2025-04-02T02:17:01.076Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6b/eaa6768e02edebaf37d77f4ffb74dd55f5cbcbb6a0dbf798ccec7b0ac23b/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34", size = 1718518, upload-time = "2025-04-02T02:17:03.388Z" }, - { url = "https://files.pythonhosted.org/packages/e5/18/dda87cbad29472a51fa058d6d8257dfce168289adaeb358b86bd93af3b20/aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913", size = 1675254, upload-time = "2025-04-02T02:17:05.579Z" }, - { url = "https://files.pythonhosted.org/packages/32/d9/d2fb08c614df401d92c12fcbc60e6e879608d5e8909ef75c5ad8d4ad8aa7/aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979", size = 410698, upload-time = "2025-04-02T02:17:07.499Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ed/853e36d5a33c24544cfa46585895547de152dfef0b5c79fa675f6e4b7b87/aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802", size = 436395, upload-time = "2025-04-02T02:17:09.566Z" }, + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, ] [[package]] @@ -104,26 +103,26 @@ wheels = [ [[package]] name = "aiohttp-cors" -version = "0.7.0" +version = "0.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/9e/6cdce7c3f346d8fd487adf68761728ad8cd5fbc296a7b07b92518350d31f/aiohttp-cors-0.7.0.tar.gz", hash = "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d", size = 35966, upload-time = "2018-03-06T15:45:42.936Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/d89e846a5444b3d5eb8985a6ddb0daef3774928e1bfbce8e84ec97b0ffa7/aiohttp_cors-0.8.1.tar.gz", hash = "sha256:ccacf9cb84b64939ea15f859a146af1f662a6b1d68175754a07315e305fb1403", size = 38626, upload-time = "2025-03-31T14:16:20.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/e7/e436a0c0eb5127d8b491a9b83ecd2391c6ff7dcd5548dfaec2080a2340fd/aiohttp_cors-0.7.0-py3-none-any.whl", hash = "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e", size = 27564, upload-time = "2018-03-06T15:45:42.034Z" }, + { url = "https://files.pythonhosted.org/packages/98/3b/40a68de458904bcc143622015fff2352b6461cd92fd66d3527bf1c6f5716/aiohttp_cors-0.8.1-py3-none-any.whl", hash = "sha256:3180cf304c5c712d626b9162b195b1db7ddf976a2a25172b35bb2448b890a80d", size = 25231, upload-time = "2025-03-31T14:16:18.478Z" }, ] [[package]] name = "aiohttp-fast-zlib" -version = "0.2.3" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/73/c93543264f745202a6fe78ad8ddb7c13a9d3e3ea47cde26501d683bd46a4/aiohttp_fast_zlib-0.2.3.tar.gz", hash = "sha256:d7e34621f2ac47155d9ad5d78f15ffb066a4ee849cb3d55df0077395ab4b3eff", size = 8591, upload-time = "2025-02-22T17:52:51.832Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/a6/982f3a013b42e914a2420631afcaecb729c49525cc6cc58e15d27ee4cb4b/aiohttp_fast_zlib-0.3.0.tar.gz", hash = "sha256:963a09de571b67fa0ef9cb44c5a32ede5cb1a51bc79fc21181b1cddd56b58b28", size = 8770, upload-time = "2025-06-07T12:41:49.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/55/9aebf9f5dac1a34bb0a4f300d2ec4692f86df44e458f3061a659dec2b98f/aiohttp_fast_zlib-0.2.3-py3-none-any.whl", hash = "sha256:41a93670f88042faff3ebbd039fd2fc37a0c956193c20eb758be45b1655a7e04", size = 8421, upload-time = "2025-02-22T17:52:49.971Z" }, + { url = "https://files.pythonhosted.org/packages/b7/11/ea9ecbcd6cf68c5de690fd39b66341405ab091aa0c3598277e687aa65901/aiohttp_fast_zlib-0.3.0-py3-none-any.whl", hash = "sha256:d4cb20760a3e1137c93cb42c13871cbc9cd1fdc069352f2712cd650d6c0e537e", size = 8615, upload-time = "2025-06-07T12:41:47.454Z" }, ] [[package]] @@ -239,11 +238,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.1.0" +version = "25.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562, upload-time = "2025-01-25T11:30:12.508Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152, upload-time = "2025-01-25T11:30:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, ] [[package]] @@ -288,11 +287,11 @@ wheels = [ [[package]] name = "awesomeversion" -version = "24.6.0" +version = "25.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/e9/1baaf8619a3d66b467ba105976897e67b36dbad93b619753768357dbd475/awesomeversion-24.6.0.tar.gz", hash = "sha256:aee7ccbaed6f8d84e0f0364080c7734a0166d77ea6ccfcc4900b38917f1efc71", size = 11997, upload-time = "2024-06-24T11:09:27.958Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/95/bd19ef0ef6735bd7131c0310f71432ea5fdf3dc2b3245a262d1f34bae55e/awesomeversion-25.5.0.tar.gz", hash = "sha256:d64c9f3579d2f60a5aa506a9dd0b38a74ab5f45e04800f943a547c1102280f31", size = 11693, upload-time = "2025-05-29T12:38:02.352Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/a5/258ffce7048e8be24c6f402bcbf5d1b3933d5d63421d000a55e74248481b/awesomeversion-24.6.0-py3-none-any.whl", hash = "sha256:6768415b8954b379a25cebf21ed4f682cab10aebf3f82a6640aaaa15ec6821f2", size = 14716, upload-time = "2024-06-24T11:09:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/dd/99/dc26ce0845a99f90fd99464a1d9124d5eacaa8bac92072af059cf002def4/awesomeversion-25.5.0-py3-none-any.whl", hash = "sha256:34a676ae10e10d3a96829fcc890a1d377fe1a7a2b98ee19951631951c2aebff6", size = 13998, upload-time = "2025-05-29T12:38:01.127Z" }, ] [[package]] @@ -319,32 +318,52 @@ wheels = [ [[package]] name = "bcrypt" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/7e/d95e7d96d4828e965891af92e43b52a4cd3395dc1c1ef4ee62748d0471d0/bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221", size = 24294, upload-time = "2024-07-22T18:09:10.445Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/81/4e8f5bc0cd947e91fb720e1737371922854da47a94bc9630454e7b2845f8/bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb", size = 471568, upload-time = "2024-07-22T18:08:55.603Z" }, - { url = "https://files.pythonhosted.org/packages/05/d2/1be1e16aedec04bcf8d0156e01b987d16a2063d38e64c3f28030a3427d61/bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00", size = 277372, upload-time = "2024-07-22T18:08:51.446Z" }, - { url = "https://files.pythonhosted.org/packages/e3/96/7a654027638ad9b7589effb6db77eb63eba64319dfeaf9c0f4ca953e5f76/bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d", size = 273488, upload-time = "2024-07-22T18:09:02.005Z" }, - { url = "https://files.pythonhosted.org/packages/46/54/dc7b58abeb4a3d95bab653405935e27ba32f21b812d8ff38f271fb6f7f55/bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291", size = 277759, upload-time = "2024-07-22T18:08:50.017Z" }, - { url = "https://files.pythonhosted.org/packages/ac/be/da233c5f11fce3f8adec05e8e532b299b64833cc962f49331cdd0e614fa9/bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328", size = 273796, upload-time = "2024-07-22T18:09:07.605Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b8/8b4add88d55a263cf1c6b8cf66c735280954a04223fcd2880120cc767ac3/bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7", size = 311082, upload-time = "2024-07-22T18:08:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/7b/76/2aa660679abbdc7f8ee961552e4bb6415a81b303e55e9374533f22770203/bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399", size = 305912, upload-time = "2024-07-22T18:08:40.049Z" }, - { url = "https://files.pythonhosted.org/packages/00/03/2af7c45034aba6002d4f2b728c1a385676b4eab7d764410e34fd768009f2/bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060", size = 325185, upload-time = "2024-07-22T18:08:41.833Z" }, - { url = "https://files.pythonhosted.org/packages/dc/5d/6843443ce4ab3af40bddb6c7c085ed4a8418b3396f7a17e60e6d9888416c/bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7", size = 335188, upload-time = "2024-07-22T18:08:29.25Z" }, - { url = "https://files.pythonhosted.org/packages/cb/4c/ff8ca83d816052fba36def1d24e97d9a85739b9bbf428c0d0ecd296a07c8/bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458", size = 156481, upload-time = "2024-07-22T18:09:00.303Z" }, - { url = "https://files.pythonhosted.org/packages/65/f1/e09626c88a56cda488810fb29d5035f1662873777ed337880856b9d204ae/bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5", size = 151336, upload-time = "2024-07-22T18:08:48.473Z" }, - { url = "https://files.pythonhosted.org/packages/96/86/8c6a84daed4dd878fbab094400c9174c43d9b838ace077a2f8ee8bc3ae12/bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841", size = 472414, upload-time = "2024-07-22T18:08:32.176Z" }, - { url = "https://files.pythonhosted.org/packages/f6/05/e394515f4e23c17662e5aeb4d1859b11dc651be01a3bd03c2e919a155901/bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68", size = 277599, upload-time = "2024-07-22T18:08:53.974Z" }, - { url = "https://files.pythonhosted.org/packages/4b/3b/ad784eac415937c53da48983756105d267b91e56aa53ba8a1b2014b8d930/bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe", size = 273491, upload-time = "2024-07-22T18:08:45.231Z" }, - { url = "https://files.pythonhosted.org/packages/cc/14/b9ff8e0218bee95e517b70e91130effb4511e8827ac1ab00b4e30943a3f6/bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2", size = 277934, upload-time = "2024-07-22T18:09:09.189Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d0/31938bb697600a04864246acde4918c4190a938f891fd11883eaaf41327a/bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c", size = 273804, upload-time = "2024-07-22T18:09:04.618Z" }, - { url = "https://files.pythonhosted.org/packages/e7/c3/dae866739989e3f04ae304e1201932571708cb292a28b2f1b93283e2dcd8/bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae", size = 311275, upload-time = "2024-07-22T18:08:43.317Z" }, - { url = "https://files.pythonhosted.org/packages/5d/2c/019bc2c63c6125ddf0483ee7d914a405860327767d437913942b476e9c9b/bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d", size = 306355, upload-time = "2024-07-22T18:09:06.053Z" }, - { url = "https://files.pythonhosted.org/packages/75/fe/9e137727f122bbe29771d56afbf4e0dbc85968caa8957806f86404a5bfe1/bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e", size = 325381, upload-time = "2024-07-22T18:08:33.904Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d4/586b9c18a327561ea4cd336ff4586cca1a7aa0f5ee04e23a8a8bb9ca64f1/bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8", size = 335685, upload-time = "2024-07-22T18:08:56.897Z" }, - { url = "https://files.pythonhosted.org/packages/24/55/1a7127faf4576138bb278b91e9c75307490178979d69c8e6e273f74b974f/bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34", size = 155857, upload-time = "2024-07-22T18:08:30.827Z" }, - { url = "https://files.pythonhosted.org/packages/1c/2a/c74052e54162ec639266d91539cca7cbf3d1d3b8b36afbfeaee0ea6a1702/bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9", size = 151717, upload-time = "2024-07-22T18:08:52.781Z" }, +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" }, + { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" }, + { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" }, + { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" }, + { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" }, + { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" }, + { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" }, + { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" }, + { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" }, + { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" }, + { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, + { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, + { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, + { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, + { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, + { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, + { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, + { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, + { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, + { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, + { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, + { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, ] [[package]] @@ -590,37 +609,37 @@ wheels = [ [[package]] name = "cryptography" -version = "44.0.1" +version = "45.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/67/545c79fe50f7af51dbad56d16b23fe33f63ee6a5d956b3cb68ea110cbe64/cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14", size = 710819, upload-time = "2025-02-11T15:50:58.39Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/27/5e3524053b4c8889da65cf7814a9d0d8514a05194a25e1e34f46852ee6eb/cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009", size = 6642022, upload-time = "2025-02-11T15:49:32.752Z" }, - { url = "https://files.pythonhosted.org/packages/34/b9/4d1fa8d73ae6ec350012f89c3abfbff19fc95fe5420cf972e12a8d182986/cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f", size = 3943865, upload-time = "2025-02-11T15:49:36.659Z" }, - { url = "https://files.pythonhosted.org/packages/6e/57/371a9f3f3a4500807b5fcd29fec77f418ba27ffc629d88597d0d1049696e/cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2", size = 4162562, upload-time = "2025-02-11T15:49:39.541Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1d/5b77815e7d9cf1e3166988647f336f87d5634a5ccecec2ffbe08ef8dd481/cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911", size = 3951923, upload-time = "2025-02-11T15:49:42.461Z" }, - { url = "https://files.pythonhosted.org/packages/28/01/604508cd34a4024467cd4105887cf27da128cba3edd435b54e2395064bfb/cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69", size = 3685194, upload-time = "2025-02-11T15:49:45.226Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3d/d3c55d4f1d24580a236a6753902ef6d8aafd04da942a1ee9efb9dc8fd0cb/cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026", size = 4187790, upload-time = "2025-02-11T15:49:48.215Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a6/44d63950c8588bfa8594fd234d3d46e93c3841b8e84a066649c566afb972/cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd", size = 3951343, upload-time = "2025-02-11T15:49:50.313Z" }, - { url = "https://files.pythonhosted.org/packages/c1/17/f5282661b57301204cbf188254c1a0267dbd8b18f76337f0a7ce1038888c/cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0", size = 4187127, upload-time = "2025-02-11T15:49:52.051Z" }, - { url = "https://files.pythonhosted.org/packages/f3/68/abbae29ed4f9d96596687f3ceea8e233f65c9645fbbec68adb7c756bb85a/cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf", size = 4070666, upload-time = "2025-02-11T15:49:56.56Z" }, - { url = "https://files.pythonhosted.org/packages/0f/10/cf91691064a9e0a88ae27e31779200b1505d3aee877dbe1e4e0d73b4f155/cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864", size = 4288811, upload-time = "2025-02-11T15:49:59.248Z" }, - { url = "https://files.pythonhosted.org/packages/38/78/74ea9eb547d13c34e984e07ec8a473eb55b19c1451fe7fc8077c6a4b0548/cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a", size = 2771882, upload-time = "2025-02-11T15:50:01.478Z" }, - { url = "https://files.pythonhosted.org/packages/cf/6c/3907271ee485679e15c9f5e93eac6aa318f859b0aed8d369afd636fafa87/cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00", size = 3206989, upload-time = "2025-02-11T15:50:03.312Z" }, - { url = "https://files.pythonhosted.org/packages/9f/f1/676e69c56a9be9fd1bffa9bc3492366901f6e1f8f4079428b05f1414e65c/cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008", size = 6643714, upload-time = "2025-02-11T15:50:05.555Z" }, - { url = "https://files.pythonhosted.org/packages/ba/9f/1775600eb69e72d8f9931a104120f2667107a0ee478f6ad4fe4001559345/cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862", size = 3943269, upload-time = "2025-02-11T15:50:08.54Z" }, - { url = "https://files.pythonhosted.org/packages/25/ba/e00d5ad6b58183829615be7f11f55a7b6baa5a06910faabdc9961527ba44/cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3", size = 4166461, upload-time = "2025-02-11T15:50:11.419Z" }, - { url = "https://files.pythonhosted.org/packages/b3/45/690a02c748d719a95ab08b6e4decb9d81e0ec1bac510358f61624c86e8a3/cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7", size = 3950314, upload-time = "2025-02-11T15:50:14.181Z" }, - { url = "https://files.pythonhosted.org/packages/e6/50/bf8d090911347f9b75adc20f6f6569ed6ca9b9bff552e6e390f53c2a1233/cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a", size = 3686675, upload-time = "2025-02-11T15:50:16.3Z" }, - { url = "https://files.pythonhosted.org/packages/e1/e7/cfb18011821cc5f9b21efb3f94f3241e3a658d267a3bf3a0f45543858ed8/cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c", size = 4190429, upload-time = "2025-02-11T15:50:19.302Z" }, - { url = "https://files.pythonhosted.org/packages/07/ef/77c74d94a8bfc1a8a47b3cafe54af3db537f081742ee7a8a9bd982b62774/cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62", size = 3950039, upload-time = "2025-02-11T15:50:22.257Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b9/8be0ff57c4592382b77406269b1e15650c9f1a167f9e34941b8515b97159/cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41", size = 4189713, upload-time = "2025-02-11T15:50:24.261Z" }, - { url = "https://files.pythonhosted.org/packages/78/e1/4b6ac5f4100545513b0847a4d276fe3c7ce0eacfa73e3b5ebd31776816ee/cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b", size = 4071193, upload-time = "2025-02-11T15:50:26.18Z" }, - { url = "https://files.pythonhosted.org/packages/3d/cb/afff48ceaed15531eab70445abe500f07f8f96af2bb35d98af6bfa89ebd4/cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7", size = 4289566, upload-time = "2025-02-11T15:50:28.221Z" }, - { url = "https://files.pythonhosted.org/packages/30/6f/4eca9e2e0f13ae459acd1ca7d9f0257ab86e68f44304847610afcb813dc9/cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9", size = 2772371, upload-time = "2025-02-11T15:50:29.997Z" }, - { url = "https://files.pythonhosted.org/packages/d2/05/5533d30f53f10239616a357f080892026db2d550a40c393d0a8a7af834a9/cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f", size = 3207303, upload-time = "2025-02-11T15:50:32.258Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/13/1f/9fa001e74a1993a9cadd2333bb889e50c66327b8594ac538ab8a04f915b7/cryptography-45.0.3.tar.gz", hash = "sha256:ec21313dd335c51d7877baf2972569f40a4291b76a0ce51391523ae358d05899", size = 744738, upload-time = "2025-05-25T14:17:24.777Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/b2/2345dc595998caa6f68adf84e8f8b50d18e9fc4638d32b22ea8daedd4b7a/cryptography-45.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:7573d9eebaeceeb55285205dbbb8753ac1e962af3d9640791d12b36864065e71", size = 7056239, upload-time = "2025-05-25T14:16:12.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/3d/ac361649a0bfffc105e2298b720d8b862330a767dab27c06adc2ddbef96a/cryptography-45.0.3-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d377dde61c5d67eb4311eace661c3efda46c62113ff56bf05e2d679e02aebb5b", size = 4205541, upload-time = "2025-05-25T14:16:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/70/3e/c02a043750494d5c445f769e9c9f67e550d65060e0bfce52d91c1362693d/cryptography-45.0.3-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae1e637f527750811588e4582988932c222f8251f7b7ea93739acb624e1487f", size = 4433275, upload-time = "2025-05-25T14:16:16.421Z" }, + { url = "https://files.pythonhosted.org/packages/40/7a/9af0bfd48784e80eef3eb6fd6fde96fe706b4fc156751ce1b2b965dada70/cryptography-45.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ca932e11218bcc9ef812aa497cdf669484870ecbcf2d99b765d6c27a86000942", size = 4209173, upload-time = "2025-05-25T14:16:18.163Z" }, + { url = "https://files.pythonhosted.org/packages/31/5f/d6f8753c8708912df52e67969e80ef70b8e8897306cd9eb8b98201f8c184/cryptography-45.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af3f92b1dc25621f5fad065288a44ac790c5798e986a34d393ab27d2b27fcff9", size = 3898150, upload-time = "2025-05-25T14:16:20.34Z" }, + { url = "https://files.pythonhosted.org/packages/8b/50/f256ab79c671fb066e47336706dc398c3b1e125f952e07d54ce82cf4011a/cryptography-45.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f8f8f0b73b885ddd7f3d8c2b2234a7d3ba49002b0223f58cfde1bedd9563c56", size = 4466473, upload-time = "2025-05-25T14:16:22.605Z" }, + { url = "https://files.pythonhosted.org/packages/62/e7/312428336bb2df0848d0768ab5a062e11a32d18139447a76dfc19ada8eed/cryptography-45.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9cc80ce69032ffa528b5e16d217fa4d8d4bb7d6ba8659c1b4d74a1b0f4235fca", size = 4211890, upload-time = "2025-05-25T14:16:24.738Z" }, + { url = "https://files.pythonhosted.org/packages/e7/53/8a130e22c1e432b3c14896ec5eb7ac01fb53c6737e1d705df7e0efb647c6/cryptography-45.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c824c9281cb628015bfc3c59335163d4ca0540d49de4582d6c2637312907e4b1", size = 4466300, upload-time = "2025-05-25T14:16:26.768Z" }, + { url = "https://files.pythonhosted.org/packages/ba/75/6bb6579688ef805fd16a053005fce93944cdade465fc92ef32bbc5c40681/cryptography-45.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:5833bb4355cb377ebd880457663a972cd044e7f49585aee39245c0d592904578", size = 4332483, upload-time = "2025-05-25T14:16:28.316Z" }, + { url = "https://files.pythonhosted.org/packages/2f/11/2538f4e1ce05c6c4f81f43c1ef2bd6de7ae5e24ee284460ff6c77e42ca77/cryptography-45.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bb5bf55dcb69f7067d80354d0a348368da907345a2c448b0babc4215ccd3497", size = 4573714, upload-time = "2025-05-25T14:16:30.474Z" }, + { url = "https://files.pythonhosted.org/packages/f5/bb/e86e9cf07f73a98d84a4084e8fd420b0e82330a901d9cac8149f994c3417/cryptography-45.0.3-cp311-abi3-win32.whl", hash = "sha256:3ad69eeb92a9de9421e1f6685e85a10fbcfb75c833b42cc9bc2ba9fb00da4710", size = 2934752, upload-time = "2025-05-25T14:16:32.204Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/063bc9ddc3d1c73e959054f1fc091b79572e716ef74d6caaa56e945b4af9/cryptography-45.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:97787952246a77d77934d41b62fb1b6f3581d83f71b44796a4158d93b8f5c490", size = 3412465, upload-time = "2025-05-25T14:16:33.888Z" }, + { url = "https://files.pythonhosted.org/packages/71/9b/04ead6015229a9396890d7654ee35ef630860fb42dc9ff9ec27f72157952/cryptography-45.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:c92519d242703b675ccefd0f0562eb45e74d438e001f8ab52d628e885751fb06", size = 7031892, upload-time = "2025-05-25T14:16:36.214Z" }, + { url = "https://files.pythonhosted.org/packages/46/c7/c7d05d0e133a09fc677b8a87953815c522697bdf025e5cac13ba419e7240/cryptography-45.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5edcb90da1843df85292ef3a313513766a78fbbb83f584a5a58fb001a5a9d57", size = 4196181, upload-time = "2025-05-25T14:16:37.934Z" }, + { url = "https://files.pythonhosted.org/packages/08/7a/6ad3aa796b18a683657cef930a986fac0045417e2dc428fd336cfc45ba52/cryptography-45.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38deed72285c7ed699864f964a3f4cf11ab3fb38e8d39cfcd96710cd2b5bb716", size = 4423370, upload-time = "2025-05-25T14:16:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/4f/58/ec1461bfcb393525f597ac6a10a63938d18775b7803324072974b41a926b/cryptography-45.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5555365a50efe1f486eed6ac7062c33b97ccef409f5970a0b6f205a7cfab59c8", size = 4197839, upload-time = "2025-05-25T14:16:41.322Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3d/5185b117c32ad4f40846f579369a80e710d6146c2baa8ce09d01612750db/cryptography-45.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e4253ed8f5948a3589b3caee7ad9a5bf218ffd16869c516535325fece163dcc", size = 3886324, upload-time = "2025-05-25T14:16:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/67/85/caba91a57d291a2ad46e74016d1f83ac294f08128b26e2a81e9b4f2d2555/cryptography-45.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cfd84777b4b6684955ce86156cfb5e08d75e80dc2585e10d69e47f014f0a5342", size = 4450447, upload-time = "2025-05-25T14:16:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d1/164e3c9d559133a38279215c712b8ba38e77735d3412f37711b9f8f6f7e0/cryptography-45.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:a2b56de3417fd5f48773ad8e91abaa700b678dc7fe1e0c757e1ae340779acf7b", size = 4200576, upload-time = "2025-05-25T14:16:46.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/e002d5ce624ed46dfc32abe1deff32190f3ac47ede911789ee936f5a4255/cryptography-45.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:57a6500d459e8035e813bd8b51b671977fb149a8c95ed814989da682314d0782", size = 4450308, upload-time = "2025-05-25T14:16:48.228Z" }, + { url = "https://files.pythonhosted.org/packages/87/ad/3fbff9c28cf09b0a71e98af57d74f3662dea4a174b12acc493de00ea3f28/cryptography-45.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f22af3c78abfbc7cbcdf2c55d23c3e022e1a462ee2481011d518c7fb9c9f3d65", size = 4325125, upload-time = "2025-05-25T14:16:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b4/51417d0cc01802304c1984d76e9592f15e4801abd44ef7ba657060520bf0/cryptography-45.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:232954730c362638544758a8160c4ee1b832dc011d2c41a306ad8f7cccc5bb0b", size = 4560038, upload-time = "2025-05-25T14:16:51.398Z" }, + { url = "https://files.pythonhosted.org/packages/80/38/d572f6482d45789a7202fb87d052deb7a7b136bf17473ebff33536727a2c/cryptography-45.0.3-cp37-abi3-win32.whl", hash = "sha256:cb6ab89421bc90e0422aca911c69044c2912fc3debb19bb3c1bfe28ee3dff6ab", size = 2924070, upload-time = "2025-05-25T14:16:53.472Z" }, + { url = "https://files.pythonhosted.org/packages/91/5a/61f39c0ff4443651cc64e626fa97ad3099249152039952be8f344d6b0c86/cryptography-45.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:d54ae41e6bd70ea23707843021c778f151ca258081586f0cfa31d936ae43d1b2", size = 3395005, upload-time = "2025-05-25T14:16:55.134Z" }, ] [[package]] @@ -660,26 +679,26 @@ wheels = [ [[package]] name = "fnv-hash-fast" -version = "1.4.0" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fnvhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/36/fa1cab334dc1228235d76a22bdeab67b6895e08eb1870821c500e86b240b/fnv_hash_fast-1.4.0.tar.gz", hash = "sha256:12a2a437263f08815bd2d5759c12e881408718bb82cfffceb0341575f2c43f0a", size = 5661, upload-time = "2025-03-05T01:09:25.465Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/85/ebcbccceb212bdc9b0d964609e319469075df2a7393dcad7048a333507b6/fnv_hash_fast-1.5.0.tar.gz", hash = "sha256:c3f0d077a5e0eee6bc12938a6f560b6394b5736f3e30db83b2eca8e0fb948a74", size = 5670, upload-time = "2025-04-23T02:04:49.804Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/05/178cf5b827f0d54caa3fbb28e6e7493ad5e0d75a165b57f8c2fe9fcd3519/fnv_hash_fast-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7f1bbfe718df3aa10aafa2c40cf6bb9581a267c914a4005fc0f5e3ce21a01a12", size = 18552, upload-time = "2025-03-05T01:15:50.836Z" }, - { url = "https://files.pythonhosted.org/packages/b6/5b/88866809455c974b5cf8c8c18c2ad293df796083703c0da8ec6c4e50135f/fnv_hash_fast-1.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:25dd05a0e7f8381b2d881b122400862da4f855b80fa9d6f5a20cda0706fde9b7", size = 18585, upload-time = "2025-03-05T01:15:52.297Z" }, - { url = "https://files.pythonhosted.org/packages/da/60/effbe5965ab6da6e6805ca18729f588bee486b8e4127b8bf52037908a3a3/fnv_hash_fast-1.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1038ab67c143f1119b2cad9fb3d909439e88f72f3b137015eb642ad91245734", size = 21761, upload-time = "2025-03-05T01:15:54.267Z" }, - { url = "https://files.pythonhosted.org/packages/a9/87/d80f730b9f535cec9a6db8c11e9e38413e0a398d9e7f2e0abde80b3587b3/fnv_hash_fast-1.4.0-cp313-cp313-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:c5ae87deee0204f2aaabfc861bf322f0f1dea078c847e10e35bc67c7becfefd6", size = 23126, upload-time = "2025-03-05T01:15:55.387Z" }, - { url = "https://files.pythonhosted.org/packages/ce/70/0c795f5a92a58f6d105078c632a9a5cf1de4bcdd42d3fec20592ed7c59f4/fnv_hash_fast-1.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a07b27755378f08e399fc124f724f3af58f8f54167f6fd525068e51ba7e9e9f", size = 22152, upload-time = "2025-03-05T01:15:56.579Z" }, - { url = "https://files.pythonhosted.org/packages/9f/72/14a8bb3844037cc6afe0ba147020ba2d450f44d29a32a0d2863583822ee2/fnv_hash_fast-1.4.0-cp313-cp313-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37cb8e67d8d4df670a699d928a4ed74b7284724d73df736a8ff9f57178e6a720", size = 19755, upload-time = "2025-03-05T01:15:57.696Z" }, - { url = "https://files.pythonhosted.org/packages/c6/f8/7cce1f63cd07c0c3d998ddd5f193b56247edd0ccb049c7549d05f559c889/fnv_hash_fast-1.4.0-cp313-cp313-manylinux_2_36_x86_64.whl", hash = "sha256:5fa7945986ae71c68eef522335d33a671ed2c33952272ea4360d0c44331f90eb", size = 21792, upload-time = "2025-03-05T01:09:23.45Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/5e3d38f4ef3c7c862ba8cbda1d64b48b8ba314d937fe967815ee8e1d85bb/fnv_hash_fast-1.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e6dd8f20122d0fba69171858438eb74f1f12f4630178ab0db9d05bd0dfc68054", size = 22437, upload-time = "2025-03-05T01:15:58.782Z" }, - { url = "https://files.pythonhosted.org/packages/89/85/bb90b080e30c4e082e3120874e218956a0b8a78c5913139b38c8056be4da/fnv_hash_fast-1.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c410adea6d70c8663a168b1f499bb3cb9ff743675921aa8b6fb99e07cd83eb45", size = 20259, upload-time = "2025-03-05T01:15:59.894Z" }, - { url = "https://files.pythonhosted.org/packages/83/00/788bb2e0f369c543a8bdbc5621a7070f31b13ee6992420e198636b42276a/fnv_hash_fast-1.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:579e5ba4265f4fc41dbe6fbe12d133d22aa7948455ef8a16fbe7bc5d1666d6d8", size = 24059, upload-time = "2025-03-05T01:16:01.121Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9d/ede35e832d6801ae8373abc8926c631cb6739e7d5e432366be44a9bf98d2/fnv_hash_fast-1.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c9568b00ab3e91af8a8409c2955b5d781f9a338c0c00428e788ff77f3baac3ec", size = 22927, upload-time = "2025-03-05T01:16:02.228Z" }, - { url = "https://files.pythonhosted.org/packages/df/5c/2aa4c0b1b1648d80567499062bfe0414384df2487e794885d5ff4dc235a4/fnv_hash_fast-1.4.0-cp313-cp313-win32.whl", hash = "sha256:c04e54d919b5e0ef2cb6a2de0fbabb3d075ee2609324a678d2471c87542bbacb", size = 18938, upload-time = "2025-03-05T01:16:04.28Z" }, - { url = "https://files.pythonhosted.org/packages/d3/7a/a666e222003f52941aa4b620f2658cc1f665a84657886e584a69613bfb58/fnv_hash_fast-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:5eec9b18aee7ed014ba0b8a2cf59d5c2f2d83af683c4ad87e9c03dd2f4f5d573", size = 20453, upload-time = "2025-03-05T01:16:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/29/8e/eb6fcf4ff3d70919cc8eed1383c68682b5831b1e89d951e6922d650edeee/fnv_hash_fast-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0294a449e672583589e8e5cce9d60dfc5e29db3fb05737ccae98deba28b7d77f", size = 18597, upload-time = "2025-04-23T02:10:26.498Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f3/e5db61ba58224fd5a47fa7a16be8ee0ad1c09deadac2f73363aefa7342a9/fnv_hash_fast-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:643002874f4620c408fdf881041e7d8b23683e56b1d588604a3640758c4e6dfe", size = 18568, upload-time = "2025-04-23T02:10:27.508Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1d/8fe9a5237dd43a0a8f236413fe0e0e33b0f4f91170e6cf9f9242ff940855/fnv_hash_fast-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13904ceb14e09c5d6092eca8f6e1a65ea8bb606328b4b86d055365f23657ca58", size = 21736, upload-time = "2025-04-23T02:10:28.825Z" }, + { url = "https://files.pythonhosted.org/packages/d7/d5/5629db362f2f515429228b564e51a404c0b7b6cad04f4896161bfb5bb974/fnv_hash_fast-1.5.0-cp313-cp313-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:5747cc25ee940eaa70c05d0b3d0a49808e952b7dd8388453980b94ea9e95e837", size = 23091, upload-time = "2025-04-23T02:10:29.875Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0c/4ba49df5da5b345cb456ea1934569472555a9c4ead4a5ae899494b52e385/fnv_hash_fast-1.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9640989256fcb9e95a383ebde372b79bb4b7e14d296e5242fb32c422a6d83480", size = 22098, upload-time = "2025-04-23T02:10:31.066Z" }, + { url = "https://files.pythonhosted.org/packages/00/3d/99d8c58f550bff0da4e51f71643fa0b2b16ef47e4e8746b0698221e01451/fnv_hash_fast-1.5.0-cp313-cp313-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e3b79e3fada2925810efd1605f265f0335cafe48f1389c96c51261b3e2e05ff", size = 19733, upload-time = "2025-04-23T02:10:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/ee/00/20389a610628b5d294811fabe1bca408a4f5fe4cb5745ae05f52c77ef1b6/fnv_hash_fast-1.5.0-cp313-cp313-manylinux_2_36_x86_64.whl", hash = "sha256:ccd18302d1a2d800f6403be7d8cb02293f2e39363bc64cd843ed040396d36f1a", size = 21731, upload-time = "2025-04-23T02:04:48.356Z" }, + { url = "https://files.pythonhosted.org/packages/41/29/0c7a0c4bd2c06d7c917d38b81a084e53176ef514d5fd9d40163be1b78d78/fnv_hash_fast-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:14c7672ae4cfaf8f88418dc23ef50977f4603c602932038ae52fae44b1b03aec", size = 22374, upload-time = "2025-04-23T02:10:33.88Z" }, + { url = "https://files.pythonhosted.org/packages/ca/12/5efe53c767def55ab00ab184b4fe04591ddabffbe6daf08476dfe18dc8fb/fnv_hash_fast-1.5.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:90fff41560a95d5262f2237259a94d0c8c662e131b13540e9db51dbec1a14912", size = 20260, upload-time = "2025-04-23T02:10:34.943Z" }, + { url = "https://files.pythonhosted.org/packages/81/00/83261b804ee585ec1de0da3226185e2934ec7a1747b6a871bb2cbd777e51/fnv_hash_fast-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9b52650bd9107cfe8a81087b6bd9fa995f0ba23dafa1a7cb343aed99c136062", size = 23974, upload-time = "2025-04-23T02:10:35.943Z" }, + { url = "https://files.pythonhosted.org/packages/84/1a/72d8716adfe349eb3762e923df6e25346311469dfd3dbca4fc05d8176ced/fnv_hash_fast-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a4b3fa3e5e3273872d021bc2d6ef26db273bdd82a1bedd49b3f798dbcb34bba", size = 22844, upload-time = "2025-04-23T02:10:36.925Z" }, + { url = "https://files.pythonhosted.org/packages/8d/65/0dd16e6b1f6d163b56b34e8c6c1af41086e8d3e5fc3b77701d24c5f5cdde/fnv_hash_fast-1.5.0-cp313-cp313-win32.whl", hash = "sha256:381175ad08ee8b0c69c14283a60a20d953c24bc19e2d80e5932eb590211c50dc", size = 18983, upload-time = "2025-04-23T02:10:37.918Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8d/179abdc6304491ea72f276e1c85f5c15269f680d1cfeda07cb9963e4a03c/fnv_hash_fast-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:db8e61e38d5eddf4a4115e82bbee35f0b1b1d5affe8736f78ffc833751746cf2", size = 20507, upload-time = "2025-04-23T02:10:38.967Z" }, ] [[package]] @@ -790,24 +809,12 @@ library = [ [package.metadata.requires-dev] dev = [ { name = "colorlog" }, - { name = "homeassistant", specifier = "==2025.4.0" }, + { name = "homeassistant", specifier = "==2025.9.0" }, { name = "ruff" }, ] docs = [{ name = "mkdocs-material" }] library = [{ name = "pytablewriter", specifier = "==0.61.0" }] -[[package]] -name = "ha-ffmpeg" -version = "3.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "async-timeout" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1e/3b/bd1284a9bc39cc119b0da551a81be6cf30dc3cfb369ce8c62fb648d7a2ea/ha_ffmpeg-3.2.2.tar.gz", hash = "sha256:80e4a77b3eda73df456ec9cc3295a898ed7cbb8cd2d59798f10e8c10a8e6c401", size = 7608, upload-time = "2024-11-08T13:32:14.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/45/66/7863e5a3713bb71c02f050f14a751b02e7a2d50eaf2109c96a1202e65d8b/ha_ffmpeg-3.2.2-py3-none-any.whl", hash = "sha256:4fd4a4f4cdaf3243d2737942f3f41f141e4437d2af1167655815dc03283b1652", size = 8749, upload-time = "2024-11-08T13:32:12.69Z" }, -] - [[package]] name = "habluetooth" version = "5.7.0" @@ -839,7 +846,7 @@ wheels = [ [[package]] name = "hass-nabucasa" -version = "0.94.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "acme" }, @@ -849,27 +856,17 @@ dependencies = [ { name = "attrs" }, { name = "ciso8601" }, { name = "cryptography" }, + { name = "josepy" }, { name = "pycognito" }, { name = "pyjwt" }, + { name = "sentence-stream" }, { name = "snitun" }, { name = "webrtc-models" }, + { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/33/ac792655794278f3d429b821a5a6f4628eb9de75fdacc222da0a13cfc9a0/hass_nabucasa-0.94.0.tar.gz", hash = "sha256:2ae8ca877dbd7c128fd49f64383e69bd86a395ed175cf73d6f33478d07491947", size = 72357, upload-time = "2025-03-03T10:54:57.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/cd/fabe44c4513600e153082e15a0b98c69d728e8fa0ed5bac7658b330f8979/hass_nabucasa-0.94.0-py3-none-any.whl", hash = "sha256:5acbe999373b81e7f6cc8d2f5918fe9db0a9e18e52e6ddb19d7857818c039c46", size = 61929, upload-time = "2025-03-03T10:54:55.93Z" }, -] - -[[package]] -name = "hassil" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml" }, - { name = "unicode-rbnf" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/f4/bf2f642321114c4ca4586efb194274905388a09b1c95e52529eba2fd4d51/hassil-2.2.3.tar.gz", hash = "sha256:8516ebde2caf72362ea566cd677cb382138be3f5d36889fee21bb313bfd7d0d8", size = 46867, upload-time = "2025-02-04T17:36:22.142Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/bc/e75262103f27b0736f4bfb12637787684268c1ceec9686d549fb40c36b3a/hass_nabucasa-1.1.0.tar.gz", hash = "sha256:c9714b54ed94c8e32a8a2eec2b3e42e57739c8fa85eda164b08b292f1f311246", size = 90993, upload-time = "2025-09-03T08:40:04.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/ae/684cf7117bdd757bb7d92c20deb528db2d42a3d018fc788f1c415421d809/hassil-2.2.3-py3-none-any.whl", hash = "sha256:d22032c5268e6bdfc7fb60fa8f52f3a955d5ca982ccbfe535ed074c593e66bdf", size = 42097, upload-time = "2025-02-04T17:36:21.09Z" }, + { url = "https://files.pythonhosted.org/packages/54/35/adba64783adaeaf4108e51d7c869397e3e1fafda7777ec54274b4fff1e05/hass_nabucasa-1.1.0-py3-none-any.whl", hash = "sha256:d10498e0f61d19369e12eba121c391cc5a79f7b71d6d3295ca0e8ff7ecfb96f6", size = 73127, upload-time = "2025-09-03T08:40:03.286Z" }, ] [[package]] @@ -884,18 +881,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/9b/9904cec885cc32c45e8c22cd7e19d9c342e30074fdb7c58f3d5b33ea1adb/home_assistant_bluetooth-1.13.1-py3-none-any.whl", hash = "sha256:cdf13b5b45f7744165677831e309ee78fbaf0c2866c6b5931e14d1e4e7dae5d7", size = 7915, upload-time = "2025-02-04T16:11:13.163Z" }, ] -[[package]] -name = "home-assistant-intents" -version = "2025.3.28" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/f1/9c13e5535bbcf4801f81d88f452581b113246e485d8ff9f9d64faffcf50f/home_assistant_intents-2025.3.28.tar.gz", hash = "sha256:3b93717525ae738f9163a2215bb0628321b86bd8418bfd64e1d5ce571b84fef4", size = 451905, upload-time = "2025-03-28T14:26:00.919Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/e5/627c5cb34ed05bbe3227834702327fab6cbed6c5d6f0c6f053a85cc2b10f/home_assistant_intents-2025.3.28-py3-none-any.whl", hash = "sha256:14f589a5a188f8b0c52f06ff8998c171fda25f8729de7a4011636295d90e7295", size = 470049, upload-time = "2025-03-28T14:25:59.107Z" }, -] - [[package]] name = "homeassistant" -version = "2025.4.0" +version = "2025.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiodns" }, @@ -918,28 +906,20 @@ dependencies = [ { name = "cronsim" }, { name = "cryptography" }, { name = "fnv-hash-fast" }, - { name = "ha-ffmpeg" }, { name = "hass-nabucasa" }, - { name = "hassil" }, { name = "home-assistant-bluetooth" }, - { name = "home-assistant-intents" }, { name = "httpx" }, { name = "ifaddr" }, { name = "jinja2" }, { name = "lru-dict" }, - { name = "mutagen" }, - { name = "numpy" }, { name = "orjson" }, { name = "packaging" }, { name = "pillow" }, { name = "propcache" }, { name = "psutil-home-assistant" }, { name = "pyjwt" }, - { name = "pymicro-vad" }, { name = "pyopenssl" }, - { name = "pyspeex-noise" }, { name = "python-slugify" }, - { name = "pyturbojpeg" }, { name = "pyyaml" }, { name = "requests" }, { name = "securetar" }, @@ -957,9 +937,9 @@ dependencies = [ { name = "yarl" }, { name = "zeroconf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f2/f1/4163474410566c1b5a0888740f87d414bd84263fa17360a3c3938cb4e65e/homeassistant-2025.4.0.tar.gz", hash = "sha256:c1f9702e4a935da061d5f95254d25c039282862a510b3643ee76fb55a2b610f3", size = 25040180, upload-time = "2025-04-02T17:19:14.907Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/d1/c3c7c042acb9adcaa51f60f8e88feee726905f2b8bf0e7f7308aa18e02ad/homeassistant-2025.9.0.tar.gz", hash = "sha256:f2afb8dc2ecedb3dc809b79a1c7099e2b0ad177631aec880904578f7716cedfc", size = 27782182, upload-time = "2025-09-03T18:30:25.159Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/5d/f346faa9fa684a0321db3800598ee62cf14b8163eab30c9fe5222ecf5425/homeassistant-2025.4.0-py3-none-any.whl", hash = "sha256:b1159c123b97fe372feadbe505cf920b87fbcd932177e5c0c86a6f6b3c6ee1b9", size = 42937231, upload-time = "2025-04-02T17:19:08.558Z" }, + { url = "https://files.pythonhosted.org/packages/40/2f/a0e924149bff1f4dbfee7d7d146811300eee649eeff83f9d4b2ad2ebd2c7/homeassistant-2025.9.0-py3-none-any.whl", hash = "sha256:51bda990b96c419f5e2a3410aa1a636e454ac5a65616ea8ec663f04956c5a725", size = 46884752, upload-time = "2025-09-03T18:30:18.917Z" }, ] [[package]] @@ -1031,15 +1011,14 @@ wheels = [ [[package]] name = "josepy" -version = "1.15.0" +version = "2.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, - { name = "pyopenssl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/8a/cd416f56cd4492878e8d62701b4ad32407c5ce541f247abf31d6e5f3b79b/josepy-1.15.0.tar.gz", hash = "sha256:46c9b13d1a5104ffbfa5853e555805c915dcde71c2cd91ce5386e84211281223", size = 59310, upload-time = "2025-01-22T23:56:23.577Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/ad/6f520aee9cc9618d33430380741e9ef859b2c560b1e7915e755c084f6bc0/josepy-2.2.0.tar.gz", hash = "sha256:74c033151337c854f83efe5305a291686cef723b4b970c43cfe7270cf4a677a9", size = 56500, upload-time = "2025-10-14T14:54:42.108Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/74/fc54f4b03cb66b0b351131fcf1797fe9d7c1e6ce9a38fd940d9bc2d9531b/josepy-1.15.0-py3-none-any.whl", hash = "sha256:878c08cedd0a892c98c6d1a90b3cb869736f9c751f68ec8901e7b05a0c040fed", size = 32774, upload-time = "2025-01-22T23:56:21.524Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b2/b5caed897fbb1cc286c62c01feca977e08d99a17230ff3055b9a98eccf1d/josepy-2.2.0-py3-none-any.whl", hash = "sha256:63e9dd116d4078778c25ca88f880cc5d95f1cab0099bebe3a34c2e299f65d10b", size = 29211, upload-time = "2025-10-14T14:54:41.144Z" }, ] [[package]] @@ -1234,63 +1213,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, ] -[[package]] -name = "mutagen" -version = "1.47.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/e6/64bc71b74eef4b68e61eb921dcf72dabd9e4ec4af1e11891bbd312ccbb77/mutagen-1.47.0.tar.gz", hash = "sha256:719fadef0a978c31b4cf3c956261b3c58b6948b32023078a2117b1de09f0fc99", size = 1274186, upload-time = "2023-09-03T16:33:33.411Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/7a/620f945b96be1f6ee357d211d5bf74ab1b7fe72a9f1525aafbfe3aee6875/mutagen-1.47.0-py3-none-any.whl", hash = "sha256:edd96f50c5907a9539d8e5bba7245f62c9f520aef333d13392a79a4f70aca719", size = 194391, upload-time = "2023-09-03T16:33:29.955Z" }, -] - -[[package]] -name = "numpy" -version = "2.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/d0/c12ddfd3a02274be06ffc71f3efc6d0e457b0409c4481596881e748cb264/numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f", size = 20233295, upload-time = "2025-01-19T00:02:09.581Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/fe/df5624001f4f5c3e0b78e9017bfab7fdc18a8d3b3d3161da3d64924dd659/numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc", size = 20899188, upload-time = "2025-01-18T23:31:15.292Z" }, - { url = "https://files.pythonhosted.org/packages/a9/80/d349c3b5ed66bd3cb0214be60c27e32b90a506946857b866838adbe84040/numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369", size = 14113972, upload-time = "2025-01-18T23:31:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/9d/50/949ec9cbb28c4b751edfa64503f0913cbfa8d795b4a251e7980f13a8a655/numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd", size = 5114294, upload-time = "2025-01-18T23:31:54.219Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f3/399c15629d5a0c68ef2aa7621d430b2be22034f01dd7f3c65a9c9666c445/numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be", size = 6648426, upload-time = "2025-01-18T23:32:06.055Z" }, - { url = "https://files.pythonhosted.org/packages/2c/03/c72474c13772e30e1bc2e558cdffd9123c7872b731263d5648b5c49dd459/numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84", size = 14045990, upload-time = "2025-01-18T23:32:38.031Z" }, - { url = "https://files.pythonhosted.org/packages/83/9c/96a9ab62274ffafb023f8ee08c88d3d31ee74ca58869f859db6845494fa6/numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff", size = 16096614, upload-time = "2025-01-18T23:33:12.265Z" }, - { url = "https://files.pythonhosted.org/packages/d5/34/cd0a735534c29bec7093544b3a509febc9b0df77718a9b41ffb0809c9f46/numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0", size = 15242123, upload-time = "2025-01-18T23:33:46.412Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6d/541717a554a8f56fa75e91886d9b79ade2e595918690eb5d0d3dbd3accb9/numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de", size = 17859160, upload-time = "2025-01-18T23:34:37.857Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a5/fbf1f2b54adab31510728edd06a05c1b30839f37cf8c9747cb85831aaf1b/numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9", size = 6273337, upload-time = "2025-01-18T23:40:10.83Z" }, - { url = "https://files.pythonhosted.org/packages/56/e5/01106b9291ef1d680f82bc47d0c5b5e26dfed15b0754928e8f856c82c881/numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369", size = 12609010, upload-time = "2025-01-18T23:40:31.34Z" }, - { url = "https://files.pythonhosted.org/packages/9f/30/f23d9876de0f08dceb707c4dcf7f8dd7588266745029debb12a3cdd40be6/numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391", size = 20924451, upload-time = "2025-01-18T23:35:26.639Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ec/6ea85b2da9d5dfa1dbb4cb3c76587fc8ddcae580cb1262303ab21c0926c4/numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39", size = 14122390, upload-time = "2025-01-18T23:36:30.596Z" }, - { url = "https://files.pythonhosted.org/packages/68/05/bfbdf490414a7dbaf65b10c78bc243f312c4553234b6d91c94eb7c4b53c2/numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317", size = 5156590, upload-time = "2025-01-18T23:36:52.637Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ec/fe2e91b2642b9d6544518388a441bcd65c904cea38d9ff998e2e8ebf808e/numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49", size = 6671958, upload-time = "2025-01-18T23:37:05.361Z" }, - { url = "https://files.pythonhosted.org/packages/b1/6f/6531a78e182f194d33ee17e59d67d03d0d5a1ce7f6be7343787828d1bd4a/numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2", size = 14019950, upload-time = "2025-01-18T23:37:38.605Z" }, - { url = "https://files.pythonhosted.org/packages/e1/fb/13c58591d0b6294a08cc40fcc6b9552d239d773d520858ae27f39997f2ae/numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7", size = 16079759, upload-time = "2025-01-18T23:38:05.757Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f2/f2f8edd62abb4b289f65a7f6d1f3650273af00b91b7267a2431be7f1aec6/numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb", size = 15226139, upload-time = "2025-01-18T23:38:38.458Z" }, - { url = "https://files.pythonhosted.org/packages/aa/29/14a177f1a90b8ad8a592ca32124ac06af5eff32889874e53a308f850290f/numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648", size = 17856316, upload-time = "2025-01-18T23:39:11.454Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/242ae8d7b97f4e0e4ab8dd51231465fb23ed5e802680d629149722e3faf1/numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4", size = 6329134, upload-time = "2025-01-18T23:39:28.128Z" }, - { url = "https://files.pythonhosted.org/packages/80/94/cd9e9b04012c015cb6320ab3bf43bc615e248dddfeb163728e800a5d96f0/numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576", size = 12696208, upload-time = "2025-01-18T23:39:51.85Z" }, -] - [[package]] name = "orjson" -version = "3.10.16" +version = "3.11.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/c7/03913cc4332174071950acf5b0735463e3f63760c80585ef369270c2b372/orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10", size = 5410415, upload-time = "2025-03-24T17:00:23.312Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/b9/ff6aa28b8c86af9526160905593a2fe8d004ac7a5e592ee0b0ff71017511/orjson-3.10.16-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:148a97f7de811ba14bc6dbc4a433e0341ffd2cc285065199fb5f6a98013744bd", size = 249289, upload-time = "2025-03-24T16:59:40.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/81/6d92a586149b52684ab8fd70f3623c91d0e6a692f30fd8c728916ab2263c/orjson-3.10.16-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1d960c1bf0e734ea36d0adc880076de3846aaec45ffad29b78c7f1b7962516b8", size = 133640, upload-time = "2025-03-24T16:59:41.469Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/b72443f4793d2e16039ab85d0026677932b15ab968595fb7149750d74134/orjson-3.10.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a318cd184d1269f68634464b12871386808dc8b7c27de8565234d25975a7a137", size = 138286, upload-time = "2025-03-24T16:59:42.769Z" }, - { url = "https://files.pythonhosted.org/packages/c3/3c/72a22d4b28c076c4016d5a52bd644a8e4d849d3bb0373d9e377f9e3b2250/orjson-3.10.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df23f8df3ef9223d1d6748bea63fca55aae7da30a875700809c500a05975522b", size = 132307, upload-time = "2025-03-24T16:59:44.143Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a2/f1259561bdb6ad7061ff1b95dab082fe32758c4bc143ba8d3d70831f0a06/orjson-3.10.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94dda8dd6d1378f1037d7f3f6b21db769ef911c4567cbaa962bb6dc5021cf90", size = 136739, upload-time = "2025-03-24T16:59:45.995Z" }, - { url = "https://files.pythonhosted.org/packages/3d/af/c7583c4b34f33d8b8b90cfaab010ff18dd64e7074cc1e117a5f1eff20dcf/orjson-3.10.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12970a26666a8775346003fd94347d03ccb98ab8aa063036818381acf5f523e", size = 138076, upload-time = "2025-03-24T16:59:47.776Z" }, - { url = "https://files.pythonhosted.org/packages/d7/59/d7fc7fbdd3d4a64c2eae4fc7341a5aa39cf9549bd5e2d7f6d3c07f8b715b/orjson-3.10.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a1431a245d856bd56e4d29ea0023eb4d2c8f71efe914beb3dee8ab3f0cd7fb", size = 142643, upload-time = "2025-03-24T16:59:49.258Z" }, - { url = "https://files.pythonhosted.org/packages/92/0e/3bd8f2197d27601f16b4464ae948826da2bcf128af31230a9dbbad7ceb57/orjson-3.10.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83655cfc247f399a222567d146524674a7b217af7ef8289c0ff53cfe8db09f0", size = 133168, upload-time = "2025-03-24T16:59:51.027Z" }, - { url = "https://files.pythonhosted.org/packages/af/a8/351fd87b664b02f899f9144d2c3dc848b33ac04a5df05234cbfb9e2a7540/orjson-3.10.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa59ae64cb6ddde8f09bdbf7baf933c4cd05734ad84dcf4e43b887eb24e37652", size = 135271, upload-time = "2025-03-24T16:59:52.449Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b0/a6d42a7d412d867c60c0337d95123517dd5a9370deea705ea1be0f89389e/orjson-3.10.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ca5426e5aacc2e9507d341bc169d8af9c3cbe88f4cd4c1cf2f87e8564730eb56", size = 412444, upload-time = "2025-03-24T16:59:53.825Z" }, - { url = "https://files.pythonhosted.org/packages/79/ec/7572cd4e20863f60996f3f10bc0a6da64a6fd9c35954189a914cec0b7377/orjson-3.10.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6fd5da4edf98a400946cd3a195680de56f1e7575109b9acb9493331047157430", size = 152737, upload-time = "2025-03-24T16:59:55.599Z" }, - { url = "https://files.pythonhosted.org/packages/a9/19/ceb9e8fed5403b2e76a8ac15f581b9d25780a3be3c9b3aa54b7777a210d5/orjson-3.10.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:980ecc7a53e567169282a5e0ff078393bac78320d44238da4e246d71a4e0e8f5", size = 137482, upload-time = "2025-03-24T16:59:57.045Z" }, - { url = "https://files.pythonhosted.org/packages/1b/78/a78bb810f3786579dbbbd94768284cbe8f2fd65167cd7020260679665c17/orjson-3.10.16-cp313-cp313-win32.whl", hash = "sha256:28f79944dd006ac540a6465ebd5f8f45dfdf0948ff998eac7a908275b4c1add6", size = 141714, upload-time = "2025-03-24T16:59:58.666Z" }, - { url = "https://files.pythonhosted.org/packages/81/9c/b66ce9245ff319df2c3278acd351a3f6145ef34b4a2d7f4b0f739368370f/orjson-3.10.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe0a145e96d51971407cb8ba947e63ead2aa915db59d6631a355f5f2150b56b7", size = 133954, upload-time = "2025-03-24T17:00:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" }, + { url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" }, + { url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" }, + { url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" }, + { url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" }, + { url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" }, + { url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" }, + { url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" }, ] [[package]] @@ -1331,29 +1274,35 @@ wheels = [ [[package]] name = "pillow" -version = "11.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715, upload-time = "2025-01-02T08:13:58.407Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640, upload-time = "2025-01-02T08:11:58.329Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437, upload-time = "2025-01-02T08:12:01.797Z" }, - { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605, upload-time = "2025-01-02T08:12:05.224Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173, upload-time = "2025-01-02T08:12:08.281Z" }, - { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145, upload-time = "2025-01-02T08:12:11.411Z" }, - { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340, upload-time = "2025-01-02T08:12:15.29Z" }, - { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906, upload-time = "2025-01-02T08:12:17.485Z" }, - { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759, upload-time = "2025-01-02T08:12:20.382Z" }, - { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657, upload-time = "2025-01-02T08:12:23.922Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304, upload-time = "2025-01-02T08:12:28.069Z" }, - { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117, upload-time = "2025-01-02T08:12:30.064Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060, upload-time = "2025-01-02T08:12:32.362Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192, upload-time = "2025-01-02T08:12:34.361Z" }, - { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805, upload-time = "2025-01-02T08:12:36.99Z" }, - { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623, upload-time = "2025-01-02T08:12:41.912Z" }, - { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191, upload-time = "2025-01-02T08:12:45.186Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494, upload-time = "2025-01-02T08:12:47.098Z" }, - { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595, upload-time = "2025-01-02T08:12:50.47Z" }, - { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651, upload-time = "2025-01-02T08:12:53.356Z" }, +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, ] [[package]] @@ -1367,43 +1316,43 @@ wheels = [ [[package]] name = "propcache" -version = "0.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/76/f941e63d55c0293ff7829dd21e7cf1147e90a526756869a9070f287a68c9/propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5", size = 42722, upload-time = "2025-02-20T19:03:29.191Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/0f/a79dd23a0efd6ee01ab0dc9750d8479b343bfd0c73560d59d271eb6a99d4/propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568", size = 77287, upload-time = "2025-02-20T19:01:40.897Z" }, - { url = "https://files.pythonhosted.org/packages/b8/51/76675703c90de38ac75adb8deceb3f3ad99b67ff02a0fa5d067757971ab8/propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9", size = 44923, upload-time = "2025-02-20T19:01:42.397Z" }, - { url = "https://files.pythonhosted.org/packages/01/9b/fd5ddbee66cf7686e73c516227c2fd9bf471dbfed0f48329d095ea1228d3/propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767", size = 44325, upload-time = "2025-02-20T19:01:43.976Z" }, - { url = "https://files.pythonhosted.org/packages/13/1c/6961f11eb215a683b34b903b82bde486c606516c1466bf1fa67f26906d51/propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8", size = 225116, upload-time = "2025-02-20T19:01:45.488Z" }, - { url = "https://files.pythonhosted.org/packages/ef/ea/f8410c40abcb2e40dffe9adeed017898c930974650a63e5c79b886aa9f73/propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0", size = 229905, upload-time = "2025-02-20T19:01:49.454Z" }, - { url = "https://files.pythonhosted.org/packages/ef/5a/a9bf90894001468bf8e6ea293bb00626cc9ef10f8eb7996e9ec29345c7ed/propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d", size = 233221, upload-time = "2025-02-20T19:01:51.142Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ce/fffdddd9725b690b01d345c1156b4c2cc6dca09ab5c23a6d07b8f37d6e2f/propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05", size = 227627, upload-time = "2025-02-20T19:01:53.695Z" }, - { url = "https://files.pythonhosted.org/packages/58/ae/45c89a5994a334735a3032b48e8e4a98c05d9536ddee0719913dc27da548/propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe", size = 214217, upload-time = "2025-02-20T19:01:55.309Z" }, - { url = "https://files.pythonhosted.org/packages/01/84/bc60188c3290ff8f5f4a92b9ca2d93a62e449c8daf6fd11ad517ad136926/propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1", size = 212921, upload-time = "2025-02-20T19:01:57.893Z" }, - { url = "https://files.pythonhosted.org/packages/14/b3/39d60224048feef7a96edabb8217dc3f75415457e5ebbef6814f8b2a27b5/propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92", size = 208200, upload-time = "2025-02-20T19:02:00.026Z" }, - { url = "https://files.pythonhosted.org/packages/9d/b3/0a6720b86791251273fff8a01bc8e628bc70903513bd456f86cde1e1ef84/propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787", size = 208400, upload-time = "2025-02-20T19:02:03.997Z" }, - { url = "https://files.pythonhosted.org/packages/e9/4f/bb470f3e687790547e2e78105fb411f54e0cdde0d74106ccadd2521c6572/propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545", size = 218116, upload-time = "2025-02-20T19:02:06.042Z" }, - { url = "https://files.pythonhosted.org/packages/34/71/277f7f9add469698ac9724c199bfe06f85b199542121a71f65a80423d62a/propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e", size = 222911, upload-time = "2025-02-20T19:02:08.748Z" }, - { url = "https://files.pythonhosted.org/packages/92/e3/a7b9782aef5a2fc765b1d97da9ec7aed2f25a4e985703608e73232205e3f/propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626", size = 216563, upload-time = "2025-02-20T19:02:11.322Z" }, - { url = "https://files.pythonhosted.org/packages/ab/76/0583ca2c551aa08ffcff87b2c6849c8f01c1f6fb815a5226f0c5c202173e/propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374", size = 39763, upload-time = "2025-02-20T19:02:12.977Z" }, - { url = "https://files.pythonhosted.org/packages/80/ec/c6a84f9a36f608379b95f0e786c111d5465926f8c62f12be8cdadb02b15c/propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a", size = 43650, upload-time = "2025-02-20T19:02:15.041Z" }, - { url = "https://files.pythonhosted.org/packages/ee/95/7d32e3560f5bf83fc2f2a4c1b0c181d327d53d5f85ebd045ab89d4d97763/propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf", size = 82140, upload-time = "2025-02-20T19:02:16.562Z" }, - { url = "https://files.pythonhosted.org/packages/86/89/752388f12e6027a5e63f5d075f15291ded48e2d8311314fff039da5a9b11/propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0", size = 47296, upload-time = "2025-02-20T19:02:17.974Z" }, - { url = "https://files.pythonhosted.org/packages/1b/4c/b55c98d586c69180d3048984a57a5ea238bdeeccf82dbfcd598e935e10bb/propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829", size = 46724, upload-time = "2025-02-20T19:02:19.588Z" }, - { url = "https://files.pythonhosted.org/packages/0f/b6/67451a437aed90c4e951e320b5b3d7eb584ade1d5592f6e5e8f678030989/propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa", size = 291499, upload-time = "2025-02-20T19:02:21.1Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ff/e4179facd21515b24737e1e26e02615dfb5ed29416eed4cf5bc6ac5ce5fb/propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6", size = 293911, upload-time = "2025-02-20T19:02:24.248Z" }, - { url = "https://files.pythonhosted.org/packages/76/8d/94a8585992a064a23bd54f56c5e58c3b8bf0c0a06ae10e56f2353ae16c3d/propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db", size = 293301, upload-time = "2025-02-20T19:02:26.034Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b8/2c860c92b4134f68c7716c6f30a0d723973f881c32a6d7a24c4ddca05fdf/propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54", size = 281947, upload-time = "2025-02-20T19:02:27.838Z" }, - { url = "https://files.pythonhosted.org/packages/cd/72/b564be7411b525d11757b713c757c21cd4dc13b6569c3b2b8f6d3c96fd5e/propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121", size = 268072, upload-time = "2025-02-20T19:02:29.594Z" }, - { url = "https://files.pythonhosted.org/packages/37/68/d94649e399e8d7fc051e5a4f2334efc567993525af083db145a70690a121/propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e", size = 275190, upload-time = "2025-02-20T19:02:32.255Z" }, - { url = "https://files.pythonhosted.org/packages/d8/3c/446e125f5bbbc1922964dd67cb541c01cdb678d811297b79a4ff6accc843/propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e", size = 254145, upload-time = "2025-02-20T19:02:33.932Z" }, - { url = "https://files.pythonhosted.org/packages/f4/80/fd3f741483dc8e59f7ba7e05eaa0f4e11677d7db2077522b92ff80117a2a/propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a", size = 257163, upload-time = "2025-02-20T19:02:35.675Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cf/6292b5ce6ed0017e6a89024a827292122cc41b6259b30ada0c6732288513/propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac", size = 280249, upload-time = "2025-02-20T19:02:38.406Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f0/fd9b8247b449fe02a4f96538b979997e229af516d7462b006392badc59a1/propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e", size = 288741, upload-time = "2025-02-20T19:02:40.149Z" }, - { url = "https://files.pythonhosted.org/packages/64/71/cf831fdc2617f86cfd7f414cfc487d018e722dac8acc098366ce9bba0941/propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf", size = 277061, upload-time = "2025-02-20T19:02:42.309Z" }, - { url = "https://files.pythonhosted.org/packages/42/78/9432542a35d944abeca9e02927a0de38cd7a298466d8ffa171536e2381c3/propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863", size = 42252, upload-time = "2025-02-20T19:02:44.447Z" }, - { url = "https://files.pythonhosted.org/packages/6f/45/960365f4f8978f48ebb56b1127adf33a49f2e69ecd46ac1f46d6cf78a79d/propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46", size = 46425, upload-time = "2025-02-20T19:02:48.071Z" }, - { url = "https://files.pythonhosted.org/packages/b5/35/6c4c6fc8774a9e3629cd750dc24a7a4fb090a25ccd5c3246d127b70f9e22/propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043", size = 12101, upload-time = "2025-02-20T19:03:27.202Z" }, +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, ] [[package]] @@ -1522,12 +1471,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/06/43084e6cbd4b3bc0e80f6be743b2e79fbc6eed8de9ad8c629939fa55d972/pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d", size = 266178, upload-time = "2025-07-28T16:19:31.401Z" }, ] -[[package]] -name = "pymicro-vad" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/0f/a92acea368e2b37fbc706f6d049f04557497d981316a2f428b26f14666a9/pymicro_vad-1.0.1.tar.gz", hash = "sha256:60e0508b338b694c7ad71c633c0da6fcd2678a88abb8e948b80fa68934965111", size = 135575, upload-time = "2024-07-31T20:04:04.619Z" } - [[package]] name = "pyobjc-core" version = "12.0" @@ -1581,14 +1524,14 @@ wheels = [ [[package]] name = "pyopenssl" -version = "25.0.0" +version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/26/e25b4a374b4639e0c235527bbe31c0524f26eda701d79456a7e1877f4cc5/pyopenssl-25.0.0.tar.gz", hash = "sha256:cd2cef799efa3936bb08e8ccb9433a575722b9dd986023f1cabc4ae64e9dac16", size = 179573, upload-time = "2025-01-12T17:22:48.897Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/8c/cd89ad05804f8e3c17dea8f178c3f40eeab5694c30e0c9f5bcd49f576fc3/pyopenssl-25.1.0.tar.gz", hash = "sha256:8d031884482e0c67ee92bf9a4d8cceb08d92aba7136432ffb0703c5280fc205b", size = 179937, upload-time = "2025-05-17T16:28:31.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/d7/eb76863d2060dcbe7c7e6cccfd95ac02ea0b9acc37745a0d99ff6457aefb/pyOpenSSL-25.0.0-py3-none-any.whl", hash = "sha256:424c247065e46e76a37411b9ab1782541c23bb658bf003772c3405fbaa128e90", size = 56453, upload-time = "2025-01-12T17:22:43.44Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2659c02301b9500751f8d42f9a6632e1508aa5120de5e43042b8b30f8d5d/pyopenssl-25.1.0-py3-none-any.whl", hash = "sha256:2b11f239acc47ac2e5aca04fd7fa829800aeee22a2eb30d744572a157bd8a1ab", size = 56771, upload-time = "2025-05-17T16:28:29.197Z" }, ] [[package]] @@ -1606,12 +1549,6 @@ version = "0.1.6.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/08/64/a99f27d3b4347486c7bfc0aa516016c46dc4c0f380ffccbd742a61af1eda/PyRIC-0.1.6.3.tar.gz", hash = "sha256:b539b01cafebd2406c00097f94525ea0f8ecd1dd92f7731f43eac0ef16c2ccc9", size = 870401, upload-time = "2016-12-04T07:54:48.374Z" } -[[package]] -name = "pyspeex-noise" -version = "1.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/1d/7d2ebb8f73c2b2e929b4ba5370b35dbc91f37268ea53f4b6acd9afa532cb/pyspeex_noise-1.0.2.tar.gz", hash = "sha256:56a888ca2ef7fdea2316aa7fad3636d2fcf5f4450f3a0db58caa7c10a614b254", size = 49882, upload-time = "2024-08-27T17:00:34.859Z" } - [[package]] name = "pytablewriter" version = "0.61.0" @@ -1654,15 +1591,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, ] -[[package]] -name = "pyturbojpeg" -version = "1.7.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/ba/37c075c7cc86b89a22db4ac46c2e4f444666f9a43975a512b7cf70ced2fd/PyTurboJPEG-1.7.5.tar.gz", hash = "sha256:5dd5f40dbf4159f41b6abaa123733910e8b1182df562b6ddb768991868b487d3", size = 12065, upload-time = "2024-07-28T08:34:03.778Z" } - [[package]] name = "pytz" version = "2025.2" @@ -1701,9 +1629,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] +[[package]] +name = "regex" +version = "2024.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" }, + { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" }, + { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" }, + { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" }, + { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" }, + { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" }, + { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" }, + { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" }, +] + [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1711,9 +1662,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] [[package]] @@ -1766,6 +1717,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/e0/b93a18e9bb7f7d2573a9c6819d42d996851edde0b0406d017067d7d23a0a/securetar-2025.2.1-py3-none-any.whl", hash = "sha256:760ad9d93579d5923f3d0da86e0f185d0f844cf01795a8754539827bb6a1bab4", size = 11545, upload-time = "2025-02-25T14:17:50.832Z" }, ] +[[package]] +name = "sentence-stream" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/61/51918209769d7373c9bcaecac6222fb494b1d1f272e818e515e5129ef89c/sentence_stream-1.1.0.tar.gz", hash = "sha256:a512604a9f43d4132e29ad04664e8b1778f4a20265799ac86e8d62d181009483", size = 9262, upload-time = "2025-07-24T15:37:37.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/c8/8e39ad90b52372ed3bd1254450ef69f55f7920a838f906e29a414ffcf4b2/sentence_stream-1.1.0-py3-none-any.whl", hash = "sha256:3fceb47673ff16f5e301d7d0935db18413f8f1143ba4aea7ea2d9f808c5f1436", size = 7989, upload-time = "2025-07-24T15:37:36.606Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -1795,38 +1758,36 @@ wheels = [ [[package]] name = "snitun" -version = "0.40.0" +version = "0.44.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, - { name = "async-timeout" }, - { name = "attrs" }, { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/5d/c39d5dee7119017efa571e7ce09fcb4f098734cb367adab59bed497ae0e9/snitun-0.40.0.tar.gz", hash = "sha256:f5a70b3aab07524f196d27baf7a8f8774b3b00c442e91392539dd11dbd033c9c", size = 33111, upload-time = "2024-12-18T12:43:16.948Z" } +sdist = { url = "https://files.pythonhosted.org/packages/28/83/acef455bd45428b512148db8c67ffdbb5e3460ab4e036dd896de15db0e7b/snitun-0.44.0.tar.gz", hash = "sha256:b9f693568ea6a7da6a9fa459597a404c1657bfb9259eb076005a8eb1247df087", size = 41098, upload-time = "2025-07-22T21:42:19.373Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/07/9982bd349e7a1aef3f8077ccfcf7ee9b447bd70ccab8121ad786334a882a/snitun-0.40.0-py3-none-any.whl", hash = "sha256:dedb58d3042d13311142b55337ad6ce6ed339e43da9dca4c4c2c83df77c64ac0", size = 39122, upload-time = "2024-12-18T12:43:12.756Z" }, + { url = "https://files.pythonhosted.org/packages/c8/77/6b58e87ea1ced25cd90bb90e1def088485fae8e35771255943a4bd9c72ab/snitun-0.44.0-py3-none-any.whl", hash = "sha256:8c351ed936c9768d68b1dc5a33ad91c1b8d57cad09f29e73e0b19df0e573c08b", size = 48365, upload-time = "2025-07-22T21:42:18.013Z" }, ] [[package]] name = "sqlalchemy" -version = "2.0.39" +version = "2.0.41" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/8e/e77fcaa67f8b9f504b4764570191e291524575ddbfe78a90fc656d671fdc/sqlalchemy-2.0.39.tar.gz", hash = "sha256:5d2d1fe548def3267b4c70a8568f108d1fed7cbbeccb9cc166e05af2abc25c22", size = 9644602, upload-time = "2025-03-11T18:27:09.744Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/47/55778362642344324a900b6b2b1b26f7f02225b374eb93adc4a363a2d8ae/sqlalchemy-2.0.39-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fe193d3ae297c423e0e567e240b4324d6b6c280a048e64c77a3ea6886cc2aa87", size = 2102484, upload-time = "2025-03-11T19:21:54.018Z" }, - { url = "https://files.pythonhosted.org/packages/1b/e1/f5f26f67d095f408138f0fb2c37f827f3d458f2ae51881546045e7e55566/sqlalchemy-2.0.39-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:79f4f502125a41b1b3b34449e747a6abfd52a709d539ea7769101696bdca6716", size = 2092955, upload-time = "2025-03-11T19:21:55.658Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c2/0db0022fc729a54fc7aef90a3457bf20144a681baef82f7357832b44c566/sqlalchemy-2.0.39-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a10ca7f8a1ea0fd5630f02feb055b0f5cdfcd07bb3715fc1b6f8cb72bf114e4", size = 3179367, upload-time = "2025-03-11T19:09:31.059Z" }, - { url = "https://files.pythonhosted.org/packages/33/b7/f33743d87d0b4e7a1f12e1631a4b9a29a8d0d7c0ff9b8c896d0bf897fb60/sqlalchemy-2.0.39-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6b0a1c7ed54a5361aaebb910c1fa864bae34273662bb4ff788a527eafd6e14d", size = 3192705, upload-time = "2025-03-11T19:32:50.795Z" }, - { url = "https://files.pythonhosted.org/packages/c9/74/6814f31719109c973ddccc87bdfc2c2a9bc013bec64a375599dc5269a310/sqlalchemy-2.0.39-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52607d0ebea43cf214e2ee84a6a76bc774176f97c5a774ce33277514875a718e", size = 3125927, upload-time = "2025-03-11T19:09:32.678Z" }, - { url = "https://files.pythonhosted.org/packages/e8/6b/18f476f4baaa9a0e2fbc6808d8f958a5268b637c8eccff497bf96908d528/sqlalchemy-2.0.39-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c08a972cbac2a14810463aec3a47ff218bb00c1a607e6689b531a7c589c50723", size = 3154055, upload-time = "2025-03-11T19:32:53.344Z" }, - { url = "https://files.pythonhosted.org/packages/b4/60/76714cecb528da46bc53a0dd36d1ccef2f74ef25448b630a0a760ad07bdb/sqlalchemy-2.0.39-cp313-cp313-win32.whl", hash = "sha256:23c5aa33c01bd898f879db158537d7e7568b503b15aad60ea0c8da8109adf3e7", size = 2075315, upload-time = "2025-03-11T18:43:16.946Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7c/76828886d913700548bac5851eefa5b2c0251ebc37921fe476b93ce81b50/sqlalchemy-2.0.39-cp313-cp313-win_amd64.whl", hash = "sha256:4dabd775fd66cf17f31f8625fc0e4cfc5765f7982f94dc09b9e5868182cb71c0", size = 2099175, upload-time = "2025-03-11T18:43:18.141Z" }, - { url = "https://files.pythonhosted.org/packages/7b/0f/d69904cb7d17e65c65713303a244ec91fd3c96677baf1d6331457fd47e16/sqlalchemy-2.0.39-py3-none-any.whl", hash = "sha256:a1c6b0a5e3e326a466d809b651c63f278b1256146a377a528b6938a279da334f", size = 1898621, upload-time = "2025-03-11T19:20:33.027Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" }, + { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" }, + { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" }, + { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" }, ] [[package]] @@ -1946,22 +1907,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/1d/c43d3e1bda52a321f6cde3526b3634602958dc8ccf1f20fd6616767fd1a1/ulid_transform-1.4.0-cp313-cp313-manylinux_2_36_x86_64.whl", hash = "sha256:9b1429ca7403696b290e4e97ffadbf8ed0b7470a97ad7e273372c3deae5bfb2f", size = 51566, upload-time = "2025-03-07T10:44:00.79Z" }, ] -[[package]] -name = "unicode-rbnf" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/1f/d952ba97832647e608700c36b22d1c4476016076c9ed1ce74ae814bea55a/unicode_rbnf-2.4.0.tar.gz", hash = "sha256:6d2f12a7581c69ea6218ee61fafcd2da46e1f9986bdcd0964c5151f7c2a938ac", size = 89069, upload-time = "2025-10-07T20:59:41.3Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/21/82f5d435808cba330668a8b69efb180e3ef9739d4998e8cd0381e8c9cb23/unicode_rbnf-2.4.0-py3-none-any.whl", hash = "sha256:0176b30ac9b7b84008d7dc0f23078055dc10d2671fdadfab5747943243e20e2d", size = 141691, upload-time = "2025-10-07T20:59:40.139Z" }, -] - [[package]] name = "urllib3" -version = "1.26.20" +version = "2.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] [[package]] @@ -1975,27 +1927,28 @@ wheels = [ [[package]] name = "uv" -version = "0.6.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/32/ffa984c2ecbcf48d0ae813adf1aad79b3ecb5ffc743362088755d64ae3be/uv-0.6.10.tar.gz", hash = "sha256:cbbb03deb30af457cd93ad299ee5c3258ade3d900b4dee1af936c8a6d87d5bcb", size = 3109190, upload-time = "2025-03-26T01:08:35.208Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/5a/5ef9324c333478608eaca8c97a374a869b861a9a614c1e6695045e06d90c/uv-0.6.10-py3-none-linux_armv6l.whl", hash = "sha256:06932d36f1afaf611522a6a7ec361dac48dc67a1147d24e9eadee9703b15faaf", size = 15825875, upload-time = "2025-03-26T01:07:49.96Z" }, - { url = "https://files.pythonhosted.org/packages/f7/2f/001f6bb4342ba50cf921bd4a338ea40e5228ea6a817bd3101fbabaf010dd/uv-0.6.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e5c2ba1922c47a245d7393465fcee942df5a8bd8b80489a7b8860ba9d60102f9", size = 15967139, upload-time = "2025-03-26T01:07:53.521Z" }, - { url = "https://files.pythonhosted.org/packages/36/6b/f66dcd28508bceed7cff48efb9dfe62a50a40a0685c41fb5e6ecd45f33cd/uv-0.6.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd8a4bcfd33a0dcae3fc0936bff8602f74e5719cf839e3df233059a0b8c8330d", size = 14796758, upload-time = "2025-03-26T01:07:56.269Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a2/13eb03e8691b098f9ee63c4d3fa3a054c48bfa05a0a52aec3df33ab52376/uv-0.6.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:4dd20c47898c15ebd4b5f48101062ea248e32513bfc61fc04bc822abfe39ce8a", size = 15252527, upload-time = "2025-03-26T01:07:58.728Z" }, - { url = "https://files.pythonhosted.org/packages/58/90/053bde333fbf9030dff1354797bd74ce3624235bcf59d7558397749a88c1/uv-0.6.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:950c9cd7b75f67e25760d2f43ad4b0ee3f8c6724fe0a9cf9eff948b3044b6a6d", size = 15560957, upload-time = "2025-03-26T01:08:01.188Z" }, - { url = "https://files.pythonhosted.org/packages/34/80/feb9ecc8ab8f9e1968d6783dd47e7ebd1dfcd0231c8b7b0efd7204625cec/uv-0.6.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acca1dca7be342b2b8e26e509aa07c3144cb009788140eee045da2aad6a0c6fe", size = 16249302, upload-time = "2025-03-26T01:08:03.734Z" }, - { url = "https://files.pythonhosted.org/packages/0f/14/9a2e40e25fba7b550cb57cce62a07ddf28350cb53e9e8bd2e70c0fbacdbb/uv-0.6.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:13ac09945976dc0df0edde7e4ba3a46107036a114117c8ff84916e55216c2e32", size = 17196146, upload-time = "2025-03-26T01:08:06.129Z" }, - { url = "https://files.pythonhosted.org/packages/5e/05/5c9cd846243aca204f96c2da13da0fb38b6143eb3827dedea0e1dc1bcf1c/uv-0.6.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:145e75b99d6b7bdce8e454a851cfcd5605ff0491d568244c66fa75ca6b071bd6", size = 16944298, upload-time = "2025-03-26T01:08:08.827Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/63233a3143535a6df34ee6dc8246ef09ee79d99b902a6cc1ee179c1898f9/uv-0.6.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666d9fe312c810bba77633dbd463dc85f5a6a0d07905726a014dc53d07c774d9", size = 21226376, upload-time = "2025-03-26T01:08:11.488Z" }, - { url = "https://files.pythonhosted.org/packages/0a/d3/7e881e2a391203a7567cf03c72213701e63923591d2072c4e7fe694c919f/uv-0.6.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b98e8884093cbfb1a1cc3f855aa22f97ec8da1a87e0e761800e165d4f9224a45", size = 16621313, upload-time = "2025-03-26T01:08:14.435Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a9/124aa76690a04cf30344386358b772cdede17d84660ae1dce8643bf64939/uv-0.6.10-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e8a8a75cf34c0814c1eabdbe651741d44fb125a6dcbe159b2da02871bbfdec7e", size = 15461518, upload-time = "2025-03-26T01:08:17.002Z" }, - { url = "https://files.pythonhosted.org/packages/ef/97/19813f2ec2faac77da5548c35d6ae039d044b973ecbb0732e3f07662fd36/uv-0.6.10-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:5260f52386e217615553f2f42740ce2f64ba439ff0fd502dc5b06250eb8ae613", size = 15524130, upload-time = "2025-03-26T01:08:19.392Z" }, - { url = "https://files.pythonhosted.org/packages/09/3f/5637bbf27ac145a09ea8eba8e0c926f7a3fe8fc4b3b1c91131c4558f4ec2/uv-0.6.10-py3-none-musllinux_1_1_i686.whl", hash = "sha256:603aebbaf6be938120c73fd36e9fd85f5e1b671d3d4638b3086f478e2bb423d9", size = 15901256, upload-time = "2025-03-26T01:08:22.141Z" }, - { url = "https://files.pythonhosted.org/packages/a2/9b/2c688a897efad60d6e0587027968c1fdb0a63f70a8bef33d0b8154cc0fcd/uv-0.6.10-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d1f1bc7d94a4a7fdd75142be71b6bf2d7e01282f322721da185d711f065d7b80", size = 16746279, upload-time = "2025-03-26T01:08:24.916Z" }, - { url = "https://files.pythonhosted.org/packages/75/d4/df57d3f40c93c21fceed94156da41183daabd15422506e0fe73236c458a1/uv-0.6.10-py3-none-win32.whl", hash = "sha256:df6560256b93441c70ea2c062975bce2307a32de280f103cedb8db4a0f542348", size = 15953827, upload-time = "2025-03-26T01:08:27.395Z" }, - { url = "https://files.pythonhosted.org/packages/8a/21/a71c95c85624544c56695ae2469745bbda834e77dfc1e29d76711409eda5/uv-0.6.10-py3-none-win_amd64.whl", hash = "sha256:d795721fdd32e0471c952b7cb02a030657b6e67625fe836f4df14a3ae4aa4921", size = 17425178, upload-time = "2025-03-26T01:08:29.898Z" }, - { url = "https://files.pythonhosted.org/packages/ce/07/e6ffe467e1e365f7dd7863c4d505b1941af8cf69c494d0dbda08ba907043/uv-0.6.10-py3-none-win_arm64.whl", hash = "sha256:5188dc7041f4166bf64182d76c32c873f750259b6e4621a1400c26ebeea8c8dd", size = 16169219, upload-time = "2025-03-26T01:08:32.812Z" }, +version = "0.8.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/a1/4dea87c10875b441d906f82df42d725a4a04c2e8ae720d9fa01e1f75e3dc/uv-0.8.9.tar.gz", hash = "sha256:54d76faf5338d1e5643a32b048c600de0cdaa7084e5909106103df04f3306615", size = 3478291, upload-time = "2025-08-12T02:32:37.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/d8/a2a24d30660b5f05f86699f86b642b1193bea1017e77e5e5d3e1c64f7bcc/uv-0.8.9-py3-none-linux_armv6l.whl", hash = "sha256:4633c693c79c57a77c52608cbca8a6bb17801bfa223326fbc5c5142654c23cc3", size = 18477020, upload-time = "2025-08-12T02:31:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/4d/21/937e590fb08ce4c82503fddb08b54613c0d42dd06c660460f8f0552dd3a7/uv-0.8.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1cdc11cbc81824e51ebb1bac35745a79048557e869ef9da458e99f1c3a96c7f9", size = 18486975, upload-time = "2025-08-12T02:31:54.804Z" }, + { url = "https://files.pythonhosted.org/packages/60/a8/e6fc3e204731aa26b09934bbdecc8d6baa58a2d9e55b59b13130bacf8e52/uv-0.8.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b20ee83e3bf294e0b1347d0b27c56ea1a4fa7eeff4361fbf1f39587d4273059", size = 17178749, upload-time = "2025-08-12T02:31:57.251Z" }, + { url = "https://files.pythonhosted.org/packages/b2/3e/3104a054bb6e866503a13114ee969d4b66227ebab19a38e3468f36c03a87/uv-0.8.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:3418315e624f60a1c4ed37987b35d5ff0d03961d380e7e7946a3378499d5d779", size = 17790897, upload-time = "2025-08-12T02:31:59.451Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/ab64cca644f40bf85fb9b3a9050aad25af7882a1d774a384fc473ef9c697/uv-0.8.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7efe01b3ed9816e07e6cd4e088472a558a1d2946177f31002b4c42cd55cb4604", size = 18124831, upload-time = "2025-08-12T02:32:02.151Z" }, + { url = "https://files.pythonhosted.org/packages/08/d1/68a001e3ad5d0601ea9ff348b54a78c8ba87fd2a6b6b5e27b379f6f3dff0/uv-0.8.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e571132495d7ab24d2f0270c559d6facd4224745d9db7dff8c20ec0c71ae105a", size = 18924774, upload-time = "2025-08-12T02:32:04.479Z" }, + { url = "https://files.pythonhosted.org/packages/ed/71/1b252e523eb875aa4ac8d06d5f8df175fa2d29e13da347d5d4823bce6c47/uv-0.8.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:67507c66837d8465daaad9f2ccd7da7af981d8c94eb8e32798f62a98c28de82d", size = 20256335, upload-time = "2025-08-12T02:32:07.12Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/062a25088b30a0fd27e4cc46baa272dd816acdec252b120d05a16d63170a/uv-0.8.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3162f495805a26fba5aacbee49c8650e1e74313c7a2e6df6aec5de9d1299087", size = 19920018, upload-time = "2025-08-12T02:32:10.041Z" }, + { url = "https://files.pythonhosted.org/packages/d8/55/90a0dc35938e68509ff8e8a49ff45b0fd13f3a44752e37d8967cd9d19316/uv-0.8.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:60eb70afeb1c66180e12a15afd706bcc0968dbefccf7ef6e5d27a1aaa765419b", size = 19235553, upload-time = "2025-08-12T02:32:12.361Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a4/2db5939a3a993a06bca0a42e2120b4385bf1a4ff54242780701759252052/uv-0.8.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011d2b2d4781555f7f7d29d2f0d6b2638fc60eeff479406ed570052664589e6a", size = 19259174, upload-time = "2025-08-12T02:32:14.697Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c9/c52249b5f40f8eb2157587ae4b997942335e4df312dfb83b16b5ebdecc61/uv-0.8.9-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:97621843e087a68c0b4969676367d757e1de43c00a9f554eb7da35641bdff8a2", size = 18048069, upload-time = "2025-08-12T02:32:16.955Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ca/524137719fb09477e57c5983fa8864f824f5858b29fc679c0416634b79f0/uv-0.8.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b1be6a7b49d23b75d598691cc5c065a9e3cdf5e6e75d7b7f42f24d758ceef3c4", size = 18943440, upload-time = "2025-08-12T02:32:19.212Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b8/877bf9a52207023a8bf9b762bed3853697ed71c5c9911a4e31231de49a23/uv-0.8.9-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:91598361309c3601382c552dc22256f70b2491ad03357b66caa4be6fdf1111dd", size = 18075581, upload-time = "2025-08-12T02:32:21.732Z" }, + { url = "https://files.pythonhosted.org/packages/96/de/272d4111ff71765bcbfd3ecb4d4fff4073f08cc38b3ecdb7272518c3fe93/uv-0.8.9-py3-none-musllinux_1_1_i686.whl", hash = "sha256:dc81df9dd7571756e34255592caab92821652face35c3f52ad05efaa4bcc39d3", size = 18420275, upload-time = "2025-08-12T02:32:24.488Z" }, + { url = "https://files.pythonhosted.org/packages/90/15/fecfc6665d1bfc5c7dbd32ff1d63413ac43d7f6d16d76fdc4d2513cbe807/uv-0.8.9-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:9ef728e0a5caa2bb129c009a68b30819552e7addf934916a466116e302748bed", size = 19354288, upload-time = "2025-08-12T02:32:27.714Z" }, + { url = "https://files.pythonhosted.org/packages/52/b5/9fef88ac0cc3ca71ff718fa7d7e90c1b3a8639b041c674825aae00d24bf5/uv-0.8.9-py3-none-win32.whl", hash = "sha256:a347c2f2630a45a3b7ceae28a78a528137edfec4847bb29da1561bd8d1f7d254", size = 18197270, upload-time = "2025-08-12T02:32:30.288Z" }, + { url = "https://files.pythonhosted.org/packages/04/0a/dacd483c9726d2b74e42ee1f186aabab508222114f3099a7610ad0f78004/uv-0.8.9-py3-none-win_amd64.whl", hash = "sha256:dc12048cdb53210d0c7218bb403ad30118b1fe8eeff3fbcc184c13c26fcc47d4", size = 20221458, upload-time = "2025-08-12T02:32:32.706Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7e/f2b35278304673dcf9e8fe84b6d15531d91c59530dcf7919111f39a8d28f/uv-0.8.9-py3-none-win_arm64.whl", hash = "sha256:53332de28e9ee00effb695a15cdc70b2455d6b5f6b596d556076b5dd1fd3aa26", size = 18805689, upload-time = "2025-08-12T02:32:35.036Z" }, ] [[package]] @@ -2009,26 +1962,26 @@ wheels = [ [[package]] name = "voluptuous-openapi" -version = "0.0.6" +version = "0.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "voluptuous" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/6f/1075651d387c1570e4603080bdf0aa15aa254c21efb2688fdb18544cf4b9/voluptuous_openapi-0.0.6.tar.gz", hash = "sha256:4078c2acef23e04ceeab1ba58252590fcdc3ba6e3ed34521e8595374ab4de884", size = 13190, upload-time = "2025-01-07T07:19:07.266Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/20/ed87b130ae62076b731521b3c4bc502e6ba8cc92def09954e4e755934804/voluptuous_openapi-0.1.0.tar.gz", hash = "sha256:84bc44107c472ba8782f7a4cb342d19d155d5fe7f92367f092cd96cc850ff1b7", size = 14656, upload-time = "2025-05-11T21:10:14.876Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5c/331c21122901d4f5f4f6869683ab9859a08498074cee6075ff3eac3027a6/voluptuous_openapi-0.0.6-py3-none-any.whl", hash = "sha256:3561bbe5f46483f4cd9f631a0bd4a3ac3d7d74bab24f41bcd09b52501f712d5e", size = 9249, upload-time = "2025-01-07T07:19:05.948Z" }, + { url = "https://files.pythonhosted.org/packages/68/3b/9e689d9fc68f0032bf5b7cbf767fc8bd4771d75cddaf01267fcc05490061/voluptuous_openapi-0.1.0-py3-none-any.whl", hash = "sha256:c3aac740286d368c90a99e007d55ddca7fcddf790d218c60ee0eeec2fcd3db2b", size = 9967, upload-time = "2025-05-11T21:10:13.647Z" }, ] [[package]] name = "voluptuous-serialize" -version = "2.6.0" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "voluptuous" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/09/c26b38ab35d9f61e9bf5c3e805215db1316dd73c77569b47ab36a40d19b1/voluptuous-serialize-2.6.0.tar.gz", hash = "sha256:79acdc58239582a393144402d827fa8efd6df0f5350cdc606d9242f6f9bca7c4", size = 7562, upload-time = "2023-02-15T21:09:08.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/70/03a9b61324e1bb8b16682455b8b953bccd1001a28e43478c86f539e26285/voluptuous_serialize-2.7.0.tar.gz", hash = "sha256:d0da959f2fd93c8f1eb779c5d116231940493b51020c2c1026bab76eb56cd09e", size = 9202, upload-time = "2025-08-17T10:43:04.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/86/355e1c65934760e2fb037219f1f360562567cf6731d281440c1d57d36856/voluptuous_serialize-2.6.0-py3-none-any.whl", hash = "sha256:85a5c8d4d829cb49186c1b5396a8a517413cc5938e1bb0e374350190cd139616", size = 6819, upload-time = "2023-02-15T21:09:06.512Z" }, + { url = "https://files.pythonhosted.org/packages/f7/41/d536d9cf39821c35cc13aff403728e60e32b2fd711c240b6b9980af1c03f/voluptuous_serialize-2.7.0-py3-none-any.whl", hash = "sha256:ee3ebecace6136f38d0bf8c20ee97155db2486c6b2d0795563fafd04a519e76f", size = 7850, upload-time = "2025-08-17T10:43:03.498Z" }, ] [[package]] @@ -2179,54 +2132,72 @@ wheels = [ [[package]] name = "yarl" -version = "1.18.3" +version = "1.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062, upload-time = "2024-12-01T20:35:23.292Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789, upload-time = "2024-12-01T20:34:11.414Z" }, - { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144, upload-time = "2024-12-01T20:34:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974, upload-time = "2024-12-01T20:34:15.234Z" }, - { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587, upload-time = "2024-12-01T20:34:17.358Z" }, - { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386, upload-time = "2024-12-01T20:34:19.842Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421, upload-time = "2024-12-01T20:34:21.975Z" }, - { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384, upload-time = "2024-12-01T20:34:24.717Z" }, - { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689, upload-time = "2024-12-01T20:34:26.886Z" }, - { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453, upload-time = "2024-12-01T20:34:29.605Z" }, - { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872, upload-time = "2024-12-01T20:34:31.454Z" }, - { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497, upload-time = "2024-12-01T20:34:34.004Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981, upload-time = "2024-12-01T20:34:36.624Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229, upload-time = "2024-12-01T20:34:38.657Z" }, - { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383, upload-time = "2024-12-01T20:34:40.501Z" }, - { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152, upload-time = "2024-12-01T20:34:42.814Z" }, - { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723, upload-time = "2024-12-01T20:34:44.699Z" }, - { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109, upload-time = "2024-12-01T20:35:20.834Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, ] [[package]] name = "zeroconf" -version = "0.146.0" +version = "0.147.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ifaddr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/ac/d4c67df0649c77f343e4976ef0a00e19a6e5c3342a6eaa6e64d7b853224f/zeroconf-0.146.0.tar.gz", hash = "sha256:a48010a1931acdba5b26e99326464788daeef96dcb7b9a44d1832352f76da49c", size = 161804, upload-time = "2025-03-05T01:47:18.095Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/52/eb/25e258fbd064e7b8f1497b9e345f29d6e44dd250a0fc5afd91aa04aafa57/zeroconf-0.146.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:faec552163f3007247ef9713fc817627d89844a782da8c1479b11ea3d3370684", size = 1840888, upload-time = "2025-03-05T02:20:56.799Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1c/1fd373e225e7282c244003683740ca58bc39270cb79fa13b13435c6dc88a/zeroconf-0.146.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0fc7b22e33b9d5d81b5aee58af6447fc1cf9b11e04dc2a04e1a5d00b3ae4d23a", size = 1697122, upload-time = "2025-03-05T02:20:58.687Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/2a42f1f88f69b11db2524469e5dc6752dc819e4fe9b985e293be74b38dee/zeroconf-0.146.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6e24daeeb926f8c7d426c46082224786e26aa5b4ce9fb573133e52ff8bae81", size = 2143632, upload-time = "2025-03-05T02:21:00.927Z" }, - { url = "https://files.pythonhosted.org/packages/81/8c/6caf3a48575c2bcf7ba2739b2cda6f117a305c03e87b53d08f19c859fae3/zeroconf-0.146.0-cp313-cp313-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:9a798ea22c24a4148364f85b46ab33541072715bf8abccae2e3fd0c069f5808f", size = 2315076, upload-time = "2025-03-05T02:21:02.419Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b3/b86fa34f8d682b1bd3e144896b2c2cfb8a6c3308c1f773a7dcdb733d677b/zeroconf-0.146.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97a536e5b3d694e342bc908149342db2aae08a6f56466db63b6dffc26d2399ae", size = 2260655, upload-time = "2025-03-05T02:21:04.68Z" }, - { url = "https://files.pythonhosted.org/packages/06/2a/9b509a9d70c9f98b1b60f8d0002ac457df8f401325be6545ecb1f8071e8a/zeroconf-0.146.0-cp313-cp313-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97a09c01424e2356c41b39f7c2fb7a743623c2d413a082e861030e28090aebb", size = 2097673, upload-time = "2025-03-05T02:21:06.287Z" }, - { url = "https://files.pythonhosted.org/packages/90/70/2fb1c0470fb4a230d9cd63e245b5d4bd349a19454d363499eb4cdee3b54a/zeroconf-0.146.0-cp313-cp313-manylinux_2_36_x86_64.whl", hash = "sha256:55c2a0087847d5c8bc00cc1e85cb1d048e8b70b09b4e949a2b763f33389819bb", size = 2307311, upload-time = "2025-03-05T01:47:15.987Z" }, - { url = "https://files.pythonhosted.org/packages/64/c3/351b7c1d07c9bf43d75bbbf18f9d07f8081373e85865b5faa78b661ae882/zeroconf-0.146.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b87d01dc8d7d10b929cc63330cf2e0f726f105a57e8d86df5d946b93a0e6280f", size = 2297954, upload-time = "2025-03-05T02:21:08.129Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2a/b17dda38b9c5b916b1491acb6f32044d198c89ee74074dec0a277f0d8f49/zeroconf-0.146.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f844214eed7b66c1db3ea4ab2dddf0d84b91c340d83b2721656f70efb8588ae4", size = 2152669, upload-time = "2025-03-05T02:21:09.702Z" }, - { url = "https://files.pythonhosted.org/packages/75/6c/ef97dcd5abdcfb6c4ea8a52d2cb08982541c43a9ec64dff1335ecc45a901/zeroconf-0.146.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cf9e85463a4fdeed8c5ea3b13e4a6c6de924d90b8b0982021e7331632f80192e", size = 2495779, upload-time = "2025-03-05T02:21:11.629Z" }, - { url = "https://files.pythonhosted.org/packages/90/f5/755bd701c69da699b6f0bd939972cd0978af6a7399174048a337de610f87/zeroconf-0.146.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fc1dd03301d370c21a8c5fbbe0a6a54a068a08384fa673d596c3f2424153aeca", size = 2459359, upload-time = "2025-03-05T02:21:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/0c/24/12f936d8ec82e3ca14291e3c6f45f2a2b3d1139f5ae19670fd4624a66f86/zeroconf-0.146.0-cp313-cp313-win32.whl", hash = "sha256:b4e70e77a67b3f39e91b5c02df82ab49a54bfc4edb1aa5779e404a711938c5af", size = 1427510, upload-time = "2025-03-05T02:21:16.184Z" }, - { url = "https://files.pythonhosted.org/packages/49/bb/9ccf706c4f3dad7b72956d5123e2b228d0411a6f977f4db410ff6b8963c0/zeroconf-0.146.0-cp313-cp313-win_amd64.whl", hash = "sha256:5274ba298d2edd5d02bb3937181a1e82deef773075b04374eac149bd40fccd96", size = 1655847, upload-time = "2025-03-05T02:21:18.359Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/e2/78/f681afade2a4e7a9ade696cf3d3dcd9905e28720d74c16cafb83b5dd5c0a/zeroconf-0.147.0.tar.gz", hash = "sha256:f517375de6bf2041df826130da41dc7a3e8772176d3076a5da58854c7d2e8d7a", size = 163958, upload-time = "2025-05-03T16:24:54.207Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/83/c6ee14c962b79f616f8f987a52244e877647db3846007fc167f481a81b7d/zeroconf-0.147.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1deedbedea7402754b3a1a05a2a1c881443451ccd600b2a7f979e97dd9fcbe6d", size = 1841229, upload-time = "2025-05-03T16:59:17.783Z" }, + { url = "https://files.pythonhosted.org/packages/91/c0/42c08a8b2c5b6052d48a5517a5d05076b8ee2c0a458ea9bd5e0e2be38c01/zeroconf-0.147.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5c57d551e65a2a9b6333b685e3b074601f6e85762e4b4a490c663f1f2e215b24", size = 1697806, upload-time = "2025-05-03T16:59:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/bf/79/d9b440786d62626f2ca4bd692b6c2bbd1e70e1124c56321bac6a2212a5eb/zeroconf-0.147.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:095bdb0cd369355ff919e3be930991b38557baaa8292d82f4a4a8567a3944f05", size = 2141482, upload-time = "2025-05-03T16:59:22.067Z" }, + { url = "https://files.pythonhosted.org/packages/48/12/ab7d31620892a7f4d446a3f0261ddb1198318348c039b4a5ec7d9d09579c/zeroconf-0.147.0-cp313-cp313-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:8ae0fe0bb947b3a128af586c76a16b5a7d027daa65e67637b042c745f9b136c4", size = 2315614, upload-time = "2025-05-03T16:59:24.091Z" }, + { url = "https://files.pythonhosted.org/packages/7b/48/2de072ee42e36328e1d80408b70eddf3df0a5b9640db188caa363b3e120f/zeroconf-0.147.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cbeea4d8d0c4f6eb5a82099d53f5729b628685039a44c1a84422080f8ec5b0d", size = 2259809, upload-time = "2025-05-03T16:59:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/02/ec/3344b1ed4e60b36dd73cb66c36299c83a356e853e728c68314061498e9cd/zeroconf-0.147.0-cp313-cp313-manylinux_2_31_armv7l.manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:728f82417800c5c5dd3298f65cf7a8fef1707123b457d3832dbdf17d38f68840", size = 2096364, upload-time = "2025-05-03T16:59:27.786Z" }, + { url = "https://files.pythonhosted.org/packages/cd/30/5f34363e2d3c25a78fc925edcc5d45d332296a756d698ccfc060bba8a7aa/zeroconf-0.147.0-cp313-cp313-manylinux_2_36_x86_64.whl", hash = "sha256:a2dc9ae96cd49b50d651a78204aafe9f41e907122dc98e719be5376b4dddec6f", size = 2307868, upload-time = "2025-05-03T16:24:52.178Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a8/9b4242ae78bd271520e019faf47d8a2b36242b3b1a7fd47ee7510d380734/zeroconf-0.147.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9570ab3203cc4bd3ad023737ef4339558cdf1f33a5d45d76ed3fe77e5fa5f57", size = 2295063, upload-time = "2025-05-03T16:59:29.695Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e6/b63e4e09d71e94bfe0d30c6fc80b0e67e3845eb630bcfb056626db070776/zeroconf-0.147.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:fd783d9bac258e79d07e2bd164c1962b8f248579392b5078fd607e7bb6760b53", size = 2152284, upload-time = "2025-05-03T16:59:31.598Z" }, + { url = "https://files.pythonhosted.org/packages/72/12/42b990cb7ad997eb9f9fff15c61abff022adc44f5d1e96bd712ed6cd85ab/zeroconf-0.147.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b4acc76063cc379774db407dce0263616518bb5135057eb5eeafc447b3c05a81", size = 2498559, upload-time = "2025-05-03T16:59:33.444Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/080619bfcfc353deeb8cf7e813eaf73e8e28ff9a8ca7b97b9f0ecbf4d1d6/zeroconf-0.147.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:acc5334cb6cb98db3917bf9a3d6b6b7fdd205f8a74fd6f4b885abb4f61098857", size = 2456548, upload-time = "2025-05-03T16:59:35.721Z" }, + { url = "https://files.pythonhosted.org/packages/35/b6/a25b703f418200edd6932d56bbd32cbd087b828579cf223348fa778fb1ff/zeroconf-0.147.0-cp313-cp313-win32.whl", hash = "sha256:7c52c523aa756e67bf18d46db298a5964291f7d868b4a970163432e7d745b992", size = 1427188, upload-time = "2025-05-03T16:59:38.756Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ba463435cdb0b38088eae56d508ec6128b9012f58cedab145b1b77e51316/zeroconf-0.147.0-cp313-cp313-win_amd64.whl", hash = "sha256:60f623af0e45fba69f5fe80d7b300c913afe7928fb43f4b9757f0f76f80f0d82", size = 1655531, upload-time = "2025-05-03T16:59:40.65Z" }, ]