[
  {
    "path": ".github/FUNDING.yaml",
    "content": "github: suaveolent\nbuy_me_a_coffee: suaveolent\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/close_inactive_issues.yml",
    "content": "name: Close inactive issues\non:\n  schedule:\n    - cron: \"30 1 * * *\"\n\njobs:\n  close-issues:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v5\n        with:\n          days-before-issue-stale: 30\n          days-before-issue-close: 14\n          stale-issue-label: \"stale\"\n          stale-issue-message: \"This issue is stale because it has been open for 30 days with no activity.\"\n          close-issue-message: \"This issue was closed because it has been inactive for 14 days since being marked as stale.\"\n          exempt-issue-labels: \"bug,documentation,enhancement,good first issue,help wanted,question\"\n          days-before-pr-stale: -1\n          days-before-pr-close: -1\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n# General\n.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/asottile/pyupgrade\n    rev: v2.3.0\n    hooks:\n      - id: pyupgrade\n        args: [--py37-plus]\n  - repo: https://github.com/psf/black\n    rev: 19.10b0\n    hooks:\n      - id: black\n        args:\n          - --safe\n          - --quiet\n        files: ^((homeassistant|script|tests)/.+)?[^/]+\\.py$\n  - repo: https://github.com/codespell-project/codespell\n    rev: v1.16.0\n    hooks:\n      - id: codespell\n        args:\n          - --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing\n          - --skip=\"./.*,*.csv,*.json\"\n          - --quiet-level=2\n        exclude_types: [csv, json]\n  - repo: https://gitlab.com/pycqa/flake8\n    rev: 3.8.1\n    hooks:\n      - id: flake8\n        additional_dependencies:\n          - flake8-docstrings==1.5.0\n          - pydocstyle==5.0.2\n        files: ^(homeassistant|script|tests)/.+\\.py$\n  - repo: https://github.com/PyCQA/bandit\n    rev: 1.6.2\n    hooks:\n      - id: bandit\n        args:\n          - --quiet\n          - --format=custom\n          - --configfile=tests/bandit.yaml\n        files: ^(homeassistant|script|tests)/.+\\.py$\n  - repo: https://github.com/pre-commit/mirrors-isort\n    rev: v4.3.21\n    hooks:\n      - id: isort\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v2.4.0\n    hooks:\n      - id: check-executables-have-shebangs\n        stages: [manual]\n      - id: check-json\n  - repo: https://github.com/pre-commit/mirrors-mypy\n    rev: v0.770\n    hooks:\n      - id: mypy\n        args:\n          - --pretty\n          - --show-error-codes\n          - --show-error-context\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 suaveolent\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Hoymiles for Home Assistant\n\nThis custom component integrates Hoymiles DTUs, HMS-XXXXW microinverters and hybrid inverters into Home Assistant, providing live inverter data.\nIt uses the [hoymiles-wifi](https://github.com/suaveolent/hoymiles-wifi) Python library to communicate directly with the devices over your local network — no cloud connection required.\n\n> [!NOTE]\n> Disclaimer: This library is not affiliated with Hoymiles. It is an independent project developed to provide tools for interacting with Hoymiles DTUs and Hoymiles HMS-XXXXW series micro-inverters featuring integrated WiFi DTU. Any trademarks or product names mentioned are the property of their respective owners.\n\n[![\"Buy Me A Coffee\"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/suaveolent)\n\n## Supported Devices\n\nThe custom component was successfully tested with:\n\n- Hoymiles HMS-400W-1T\n- Hoymiles HMS-800W-2T\n- Hoymiles HMS-1000W-2T\n- Hoymiles HMS-2000DW-4T\n- Hoymiles DTU-WLite\n- Hoymiles DTU-Pro (S)\n- Hoymiles HAS-5.0LV-EUG1\n- Hoymiles HYS-4.6LV-EUG1\n- Hoymiles HYT-5.0HV-EUG1\n- Hoymiles HAT-8.0HV-EUG1\n- Solenso H-1000 (not tested for command, only to get data)\n- Solenso DTU_SLS (not tested for command, only to get data)\n\n## Warning\n\n> [!CAUTION]\n> Please refrain from using the current power limitation feature for zero feed-in, as it may lead to damaging the inverter due to excessive writes to the EEPROM.\n\n## Installation\n\n1. Open the [HACS](https://hacs.xyz) panel in your Home Assistant frontend.\n\n2. Navigate to the \"Integrations\" tab.\n\n3. Click the three dots in the top-right corner and select \"Custom Repositories.\"\n\n4. Add a new custom repository:\n\n- **URL:** `https://github.com/suaveolent/ha-hoymiles-wifi`\n\n- **Category:** Integration\n\n5. Click \"Add\"\n\n6. Click on the `Hoymiles` integration.\n\n7. Click \"DOWNLOAD\"\n\n8. Navigate to \"Settings\" - \"Devices & Services\"\n\n9. Click \"ADD INTEGRATION\" and select the `Hoymiles` integration.\n\n10. Insert IP address of hoymiles DTUBI-xxxx in field Host and click on SUBMIT\n\n> [!NOTE]\n> Sometimes the necessary lib\n> (https://github.com/suaveolent/hoymiles-wifi) is not correctly\n> installed. In this case you need to manually install the library by\n> running the `pip install hoymiles-wifi` command yourself.\n\n### Option 2: Manual Installation\n\n1. Download the contents of this repository as a ZIP file.\n\n2. Extract the ZIP file.\n\n3. Copy the entire `custom_components/hoymiles-wifi` directory to your Home Assistant\n\n4. Install the python requirements\n\n5. Restart your Home Assistant instance to apply the changes.\n\n### Docker Users: Workaround for HTTP 500 Error\n\nIf you encounter an HTTP 500 error when adding the integration in a Home Assistant Docker container, follow this workaround:\n\n1. Create a new Docker image for Home Assistant with the `hoymiles-wifi` library pre-installed:\n   ```dockerfile\n   FROM homeassistant/home-assistant\n   RUN pip install hoymiles-wifi\n   ```\n2. Build the new Docker image:\n   ```bash\n   docker build -t ha-hoymiles .\n   ```\n3. Switch to this newly built image when running Home Assistant.\n\n## Configuration\n\nConfiguration is done in the UI.\n\n1. `Host`: Enter the IP address or the hostname of your inverter or DTU.\n\n> [!NOTE]\n> To find the IP address or hostname of your inverter/DTU, you can either access your router’s web interface to view connected devices, or use a network scanning tool (such as Fing or Angry IP Scanner) to identify the device on your local network.\n\n2. `Update interval (seconds)`: This defines how frequently the system will request data from the inverter or DTU. Enter the desired time in seconds.\n\n> [!NOTE]\n> Setting the update interval below approximately 32 seconds may disable Hoymiles cloud functionality. To ensure proper communication with Hoymiles servers, keep the update interval at or above this threshold.\n\n## Screenshots\n\n![Integration](/screenshots/integration.png?raw=true)\n![Devices](/screenshots/devices.png?raw=true)\n![Device](/screenshots/device.png?raw=true)\n\n## Caution\n\nUse this custom component responsibly and be aware of potential risks. There are no guarantees provided, and any misuse or incorrect implementation may result in undesirable outcomes. Ensure that your inverter is not compromised during communication.\n\n## Known Limitations\n\n> [!NOTE]\n> **Update Frequency:** The library may experience limitations in fetching updates, potentially around twice per minute. The inverter firmware may enforce a mandatory wait period of approximately 30 seconds between requests.\n> This issue can be identified when the data returned matches the response from the previous request.\n> If you encounter this, you can try the _experimental_ performance data mode. (Needs to be enabled on each reboot of the DTU.)\n\n> [!NOTE]\n> **Compatibility:** While developed for the HMS-800W-2T inverter, compatibility with other inverters from the series is untested at the time of writing. Exercise caution and conduct thorough testing if using with different inverter models.\n\n## Attribution\n\nThis project was generated from [@oncleben31](https://github.com/oncleben31)'s [Home Assistant Custom Component Cookiecutter](https://github.com/oncleben31/cookiecutter-homeassistant-custom-component) template.\n"
  },
  {
    "path": "custom_components/__init__.py",
    "content": ""
  },
  {
    "path": "custom_components/hoymiles_wifi/__init__.py",
    "content": "\"\"\"Platform for retrieving values of a Hoymiles inverter.\"\"\"\n\nfrom datetime import timedelta\nimport logging\nimport voluptuous as vol\n\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.const import CONF_HOST, Platform\nfrom homeassistant.core import HomeAssistant\nfrom homeassistant.helpers.device_registry import DeviceEntry\nfrom homeassistant.helpers.typing import ConfigType\nfrom homeassistant.helpers import config_validation as cv\nfrom homeassistant.helpers.service import SupportsResponse\nfrom hoymiles_wifi.dtu import DTU\n\nfrom .const import (\n    CONF_DTU_SERIAL_NUMBER,\n    CONF_HYBRID_INVERTERS,\n    CONF_INVERTERS,\n    CONF_METERS,\n    CONF_PORTS,\n    CONF_THREE_PHASE_INVERTERS,\n    CONF_TIMEOUT,\n    CONF_UPDATE_INTERVAL,\n    CONFIG_VERSION,\n    CONF_IS_ENCRYPTED,\n    CONF_ENC_RAND,\n    DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS,\n    DEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS,\n    DEFAULT_TIMEOUT_SECONDS,\n    DOMAIN,\n    HASS_APP_INFO_COORDINATOR,\n    HASS_CONFIG_COORDINATOR,\n    HASS_DATA_COORDINATOR,\n    HASS_DTU,\n    HASS_ENERGY_STORAGE_DATA_COORDINATOR,\n)\nfrom .coordinator import (\n    HoymilesAppInfoUpdateCoordinator,\n    HoymilesConfigUpdateCoordinator,\n    HoymilesRealDataUpdateCoordinator,\n    HoymilesEnergyStorageUpdateCoordinator,\n)\nfrom .error import CannotConnect\nfrom .services import async_handle_set_bms_mode\nfrom .util import async_get_config_entry_data_for_host\n\n_LOGGER = logging.getLogger(__name__)\n\nPLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.BINARY_SENSOR, Platform.BUTTON]\n\nSET_BMS_SCHEMA = vol.Schema(\n    {\n        vol.Required(\"bms_mode\"): vol.In(\n            (\n                \"self_use\",\n                \"economic\",\n                \"backup_power\",\n                \"pure_off_grid\",\n                \"forced_charging\",\n                \"forced_discharge\",\n                \"peak_shaving\",\n                \"time_of_use\",\n            )\n        ),\n        vol.Required(\"rev_soc\"): vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),\n        vol.Optional(\"max_power\"): vol.All(vol.Coerce(int), vol.Range(min=0)),\n        vol.Optional(\"peak_soc\"): vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),\n        vol.Optional(\"peak_meter_power\"): vol.All(vol.Coerce(int), vol.Range(min=0)),\n        vol.Optional(\"time_settings\"): str,\n        vol.Optional(\"time_periods\"): str,\n        vol.Optional(\"device_id\"): cv.ensure_list,\n    }\n)\n\n\nasync def async_setup(hass: HomeAssistant, config: ConfigType):\n    \"\"\"Set up this integration using YAML is not supported.\"\"\"\n    return True\n\n\nasync def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):\n    \"\"\"Set up this integration using UI.\"\"\"\n\n    hass.data.setdefault(DOMAIN, {})\n\n    hass_data = dict(config_entry.data)\n\n    host = config_entry.data.get(CONF_HOST)\n    update_interval = timedelta(seconds=config_entry.data.get(CONF_UPDATE_INTERVAL))\n    single_phase_inverters = config_entry.data[CONF_INVERTERS]\n    three_phase_inverters = config_entry.data.get(CONF_THREE_PHASE_INVERTERS, [])\n    hybrid_inverters = config_entry.data.get(CONF_HYBRID_INVERTERS, [])\n    meters = config_entry.data.get(CONF_METERS, [])\n    is_encrypted = config_entry.data.get(CONF_IS_ENCRYPTED, False)\n    enc_rand = config_entry.data.get(CONF_ENC_RAND, None)\n    timeout = config_entry.data.get(CONF_TIMEOUT, DEFAULT_TIMEOUT_SECONDS)\n\n    if is_encrypted:\n        dtu = DTU(\n            host,\n            is_encrypted=is_encrypted,\n            enc_rand=bytes.fromhex(enc_rand),\n            timeout=timeout,\n        )\n    else:\n        dtu = DTU(host, timeout=timeout)\n\n    hass_data[HASS_DTU] = dtu\n\n    if single_phase_inverters or three_phase_inverters or meters:\n        data_coordinator = HoymilesRealDataUpdateCoordinator(\n            hass, dtu=dtu, config_entry=config_entry, update_interval=update_interval\n        )\n        hass_data[HASS_DATA_COORDINATOR] = data_coordinator\n\n        config_update_interval = timedelta(\n            seconds=DEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS\n        )\n        config_coordinator = HoymilesConfigUpdateCoordinator(\n            hass=hass,\n            dtu=dtu,\n            config_entry=config_entry,\n            update_interval=config_update_interval,\n        )\n        hass_data[HASS_CONFIG_COORDINATOR] = config_coordinator\n\n        app_info_update_interval = timedelta(\n            seconds=DEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS\n        )\n        app_info_update_coordinator = HoymilesAppInfoUpdateCoordinator(\n            hass=hass,\n            dtu=dtu,\n            config_entry=config_entry,\n            update_interval=app_info_update_interval,\n        )\n        hass_data[HASS_APP_INFO_COORDINATOR] = app_info_update_coordinator\n\n    if hybrid_inverters:\n        energy_storage_data_coordinator = HoymilesEnergyStorageUpdateCoordinator(\n            hass=hass,\n            dtu=dtu,\n            config_entry=config_entry,\n            update_interval=update_interval,\n            dtu_serial_number=config_entry.data[CONF_DTU_SERIAL_NUMBER],\n            inverters=hybrid_inverters,\n        )\n\n        hass_data[HASS_ENERGY_STORAGE_DATA_COORDINATOR] = (\n            energy_storage_data_coordinator\n        )\n\n    _LOGGER.debug(f\"  hass_data: {hass_data}\")  # --- IGNORE ---\n    _LOGGER.debug(f\"  config_entry_id: {config_entry.entry_id}\")\n\n    hass.data[DOMAIN][config_entry.entry_id] = hass_data\n    await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)\n\n    if single_phase_inverters or three_phase_inverters or meters:\n        await data_coordinator.async_config_entry_first_refresh()\n        await config_coordinator.async_config_entry_first_refresh()\n        await app_info_update_coordinator.async_config_entry_first_refresh()\n    if hybrid_inverters:\n        await energy_storage_data_coordinator.async_config_entry_first_refresh()\n        hass.services.async_register(\n            domain=DOMAIN,\n            service=\"set_bms_mode\",\n            service_func=async_handle_set_bms_mode,\n            schema=SET_BMS_SCHEMA,\n            supports_response=SupportsResponse.NONE,\n        )\n        _LOGGER.debug(\"Service set_bms_mode registered\")\n\n    return True\n\n\nasync def async_remove_config_entry_device(\n    hass: HomeAssistant, config_entry: ConfigEntry, device_entry: DeviceEntry\n) -> bool:\n    \"\"\"Remove a config entry from a device.\"\"\"\n    return True\n\n\nasync def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:\n    \"\"\"Migrate old entry data to the new entry schema.\"\"\"\n\n    data = config_entry.data.copy()\n\n    current_version = data.get(\"version\", 1)\n\n    if current_version != CONFIG_VERSION:\n        _LOGGER.info(\n            \"Migrating entry %s to version %s\", config_entry.entry_id, CONFIG_VERSION\n        )\n        new = {**config_entry.data}\n\n        host = config_entry.data.get(CONF_HOST)\n        try:\n            (\n                dtu_sn,\n                single_phase_inverters,\n                three_phase_inverters,\n                ports,\n                meters,\n                hybrid_inverters,\n                is_encrypted,\n                enc_rand,\n            ) = await async_get_config_entry_data_for_host(host)\n        except CannotConnect:\n            _LOGGER.error(\n                \"Could not retrieve real data information data from inverter: %s. Please ensure inverter is available!\",\n                host,\n            )\n            return False\n\n        new[CONF_DTU_SERIAL_NUMBER] = dtu_sn\n        new[CONF_INVERTERS] = single_phase_inverters\n        new[CONF_THREE_PHASE_INVERTERS] = three_phase_inverters\n        new[CONF_PORTS] = ports\n        new[CONF_METERS] = meters\n        new[CONF_HYBRID_INVERTERS] = hybrid_inverters\n        new[CONF_IS_ENCRYPTED] = is_encrypted\n        new[CONF_ENC_RAND] = enc_rand\n\n        hass.config_entries.async_update_entry(\n            config_entry, data=new, version=CONFIG_VERSION\n        )\n        _LOGGER.info(\n            \"Migration of entry %s to version %s successful\",\n            config_entry.entry_id,\n            CONFIG_VERSION,\n        )\n\n    return True\n\n\nasync def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:\n    \"\"\"Unload a config entry.\"\"\"\n    unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)\n\n    return unload_ok\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/binary_sensor.py",
    "content": "\"\"\"Contains binary sensor entities for Hoymiles WiFi integration.\"\"\"\n\nimport dataclasses\nfrom dataclasses import dataclass\nimport logging\n\nfrom homeassistant.components.binary_sensor import (\n    BinarySensorDeviceClass,\n    BinarySensorEntity,\n    BinarySensorEntityDescription,\n)\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.const import EntityCategory\nfrom homeassistant.core import HomeAssistant, callback\nfrom homeassistant.helpers.entity_platform import AddEntitiesCallback\nfrom hoymiles_wifi.dtu import NetworkState\n\nfrom .const import (\n    CONF_DTU_SERIAL_NUMBER,\n    DOMAIN,\n    HASS_DATA_COORDINATOR,\n    HASS_ENERGY_STORAGE_DATA_COORDINATOR,\n)\nfrom .entity import HoymilesCoordinatorEntity, HoymilesEntityDescription\n\n_LOGGER = logging.getLogger(__name__)\n\n\n@dataclass(frozen=True)\nclass HoymilesBinarySensorEntityDescription(\n    HoymilesEntityDescription, BinarySensorEntityDescription\n):\n    \"\"\"Describes Homiles binary sensor entity.\"\"\"\n\n\nBINARY_SENSORS = (\n    HoymilesBinarySensorEntityDescription(\n        key=\"DTU\",\n        translation_key=\"dtu\",\n        device_class=BinarySensorDeviceClass.CONNECTIVITY,\n        entity_category=EntityCategory.DIAGNOSTIC,\n        is_dtu_sensor=True,\n    ),\n)\n\n\nasync def async_setup_entry(\n    hass: HomeAssistant,\n    config_entry: ConfigEntry,\n    async_add_entities: AddEntitiesCallback,\n) -> None:\n    \"\"\"Set up sensor platform.\"\"\"\n    hass_data = hass.data[DOMAIN][config_entry.entry_id]\n    coordinator = hass_data.get(HASS_DATA_COORDINATOR, None)\n    if coordinator is None:\n        coordinator = hass_data.get(HASS_ENERGY_STORAGE_DATA_COORDINATOR, None)\n\n    dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER]\n\n    hass_data = hass.data[DOMAIN][config_entry.entry_id]\n\n    sensors = []\n\n    for description in BINARY_SENSORS:\n        updated_description = dataclasses.replace(\n            description, serial_number=dtu_serial_number\n        )\n        sensors.append(\n            HoymilesInverterSensorEntity(config_entry, updated_description, coordinator)\n        )\n\n    async_add_entities(sensors)\n\n\nclass HoymilesInverterSensorEntity(HoymilesCoordinatorEntity, BinarySensorEntity):\n    \"\"\"Represents a binary sensor entity for Hoymiles WiFi integration.\"\"\"\n\n    def __init__(\n        self,\n        config_entry: ConfigEntry,\n        description: HoymilesBinarySensorEntityDescription,\n        coordinator: HoymilesCoordinatorEntity,\n    ):\n        \"\"\"Initialize the HoymilesInverterSensorEntity.\"\"\"\n        super().__init__(config_entry, description, coordinator)\n        self._dtu = coordinator.get_dtu()\n        self._native_value = None\n\n        self.update_state_value()\n\n    @callback\n    def _handle_coordinator_update(self) -> None:\n        \"\"\"Handle updated data from the coordinator.\"\"\"\n        self.update_state_value()\n        super()._handle_coordinator_update()\n\n    @property\n    def is_on(self):\n        \"\"\"Return the state of the binary sensor.\"\"\"\n        return self._native_value\n\n    def update_state_value(self):\n        \"\"\"Update the state value of the binary sensor based on the DTU's network state.\"\"\"\n        dtu_state = self._dtu.get_state()\n        if dtu_state == NetworkState.Online:\n            self._native_value = True\n        elif dtu_state == NetworkState.Offline:\n            self._native_value = False\n        else:\n            self._native_value = None\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/button.py",
    "content": "\"\"\"Support for Hoymiles buttons.\"\"\"\n\nimport dataclasses\nfrom inspect import signature\nfrom dataclasses import dataclass\n\nfrom homeassistant.components.button import (\n    ButtonDeviceClass,\n    ButtonEntity,\n    ButtonEntityDescription,\n)\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.core import HomeAssistant\nfrom homeassistant.helpers.entity_platform import AddEntitiesCallback\nfrom hoymiles_wifi.dtu import DTU\n\nfrom .const import (\n    CONF_DTU_SERIAL_NUMBER,\n    CONF_INVERTERS,\n    CONF_THREE_PHASE_INVERTERS,\n    DOMAIN,\n    HASS_DTU,\n)\nfrom .entity import HoymilesEntity, HoymilesEntityDescription\n\n\n@dataclass(frozen=True)\nclass HoymilesButtonEntityDescription(\n    HoymilesEntityDescription, ButtonEntityDescription\n):\n    \"\"\"Class to describe a Hoymiles Button entity.\"\"\"\n\n    action: str = \"\"\n\n\nBUTTONS: tuple[HoymilesButtonEntityDescription, ...] = (\n    HoymilesButtonEntityDescription(\n        key=\"restart_dtu\",\n        translation_key=\"restart\",\n        device_class=ButtonDeviceClass.RESTART,\n        is_dtu_sensor=True,\n        action=\"async_restart_dtu\",\n    ),\n    HoymilesButtonEntityDescription(\n        key=\"turn_off_inverter_<inverter_serial>\",\n        translation_key=\"turn_off\",\n        icon=\"mdi:power-off\",\n        action=\"async_turn_off_inverter\",\n    ),\n    HoymilesButtonEntityDescription(\n        key=\"turn_on_inverter_<inverter_serial>\",\n        translation_key=\"turn_on\",\n        icon=\"mdi:power-on\",\n        action=\"async_turn_on_inverter\",\n    ),\n    HoymilesButtonEntityDescription(\n        key=\"reboot_inverter_<inverter_serial>\",\n        translation_key=\"restart\",\n        icon=\"mdi:restart\",\n        action=\"async_reboot_inverter\",\n    ),\n    HoymilesButtonEntityDescription(\n        key=\"enable_performance_data_mode\",\n        translation_key=\"enable_performance_data_mode\",\n        is_dtu_sensor=True,\n        action=\"async_enable_performance_data_mode\",\n    ),\n)\n\n\nasync def async_setup_entry(\n    hass: HomeAssistant,\n    config_entry: ConfigEntry,\n    async_add_entities: AddEntitiesCallback,\n) -> None:\n    \"\"\"Set up the Hoymiles number entities.\"\"\"\n    hass_data = hass.data[DOMAIN][config_entry.entry_id]\n    dtu = hass_data[HASS_DTU]\n    dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER]\n    single_phase_inverters = config_entry.data.get(CONF_INVERTERS, [])\n    three_phase_inverters = config_entry.data.get(CONF_THREE_PHASE_INVERTERS, [])\n    inverters = single_phase_inverters + three_phase_inverters\n\n    if inverters:\n        buttons = []\n        for description in BUTTONS:\n            if description.is_dtu_sensor is True:\n                updated_description = dataclasses.replace(\n                    description, serial_number=dtu_serial_number\n                )\n                buttons.append(\n                    HoymilesButtonEntity(config_entry, updated_description, dtu)\n                )\n            else:\n                for inverter_serial in inverters:\n                    new_key = description.key.replace(\n                        \"<inverter_serial>\", inverter_serial\n                    )\n                    updated_description = dataclasses.replace(\n                        description, key=new_key, serial_number=inverter_serial\n                    )\n                    buttons.append(\n                        HoymilesButtonEntity(config_entry, updated_description, dtu)\n                    )\n        async_add_entities(buttons)\n\n\nclass HoymilesButtonEntity(HoymilesEntity, ButtonEntity):\n    \"\"\"Hoymiles Number entity.\"\"\"\n\n    def __init__(\n        self,\n        config_entry: ConfigEntry,\n        description: HoymilesButtonEntityDescription,\n        dtu: DTU,\n    ) -> None:\n        \"\"\"Initialize the HoymilesButtonEntity.\"\"\"\n        super().__init__(config_entry, description)\n        self._dtu = dtu\n\n    async def async_press(self) -> None:\n        \"\"\"Press the button.\"\"\"\n\n        if hasattr(self._dtu, self.entity_description.action) and callable(\n            getattr(self._dtu, self.entity_description.action)\n        ):\n            method = getattr(self._dtu, self.entity_description.action)\n            method_signature = signature(method)\n            params = method_signature.parameters\n            if \"inverter_serial\" in params:\n                await method(self.entity_description.serial_number)\n            else:\n                await method()\n        else:\n            raise NotImplementedError(\n                f\"Method '{self.entity_description.action}' not implemented in DTU class.\"\n            )\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/config_flow.py",
    "content": "\"\"\"Config flow for Hoymiles.\"\"\"\n\nfrom datetime import timedelta\nimport logging\nfrom typing import Any\n\nimport voluptuous as vol\n\nfrom homeassistant.config_entries import ConfigFlow, ConfigFlowResult\nfrom homeassistant.const import CONF_HOST\nfrom homeassistant.data_entry_flow import FlowResult\n\nfrom .const import (\n    CONF_DTU_SERIAL_NUMBER,\n    CONF_HYBRID_INVERTERS,\n    CONF_INVERTERS,\n    CONF_METERS,\n    CONF_PORTS,\n    CONF_THREE_PHASE_INVERTERS,\n    CONF_TIMEOUT,\n    CONF_UPDATE_INTERVAL,\n    CONF_IS_ENCRYPTED,\n    CONF_ENC_RAND,\n    CONFIG_VERSION,\n    DEFAULT_TIMEOUT_SECONDS,\n    DEFAULT_UPDATE_INTERVAL_SECONDS,\n    DOMAIN,\n    MIN_UPDATE_INTERVAL_SECONDS,\n    MIN_TIMEOUT_SECONDS,\n)\nfrom .error import CannotConnect\nfrom .util import async_get_config_entry_data_for_host\n\n_LOGGER = logging.getLogger(__name__)\n\nDATA_SCHEMA = vol.Schema(\n    {\n        vol.Required(CONF_HOST): str,\n        vol.Optional(\n            CONF_UPDATE_INTERVAL,\n            default=timedelta(seconds=DEFAULT_UPDATE_INTERVAL_SECONDS).seconds,\n        ): vol.All(\n            vol.Coerce(int),\n            vol.Range(min=timedelta(seconds=MIN_UPDATE_INTERVAL_SECONDS).seconds),\n        ),\n        vol.Optional(\n            CONF_TIMEOUT,\n            default=timedelta(seconds=DEFAULT_TIMEOUT_SECONDS).seconds,\n        ): vol.All(\n            vol.Coerce(int),\n            vol.Range(min=timedelta(seconds=MIN_TIMEOUT_SECONDS).seconds),\n        ),\n    }\n)\n\n\nclass HoymilesInverterConfigFlowHandler(ConfigFlow, domain=DOMAIN):\n    \"\"\"Hoymiles Inverter config flow.\"\"\"\n\n    VERSION = CONFIG_VERSION\n\n    async def async_step_user(\n        self, user_input: dict[str, Any] | None = None\n    ) -> FlowResult:\n        \"\"\"Handle a flow initiated by the user.\"\"\"\n        errors = {}\n\n        if user_input is not None:\n            host = user_input[CONF_HOST]\n            update_interval = user_input.get(\n                CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_SECONDS\n            )\n            timeout = user_input.get(CONF_TIMEOUT, DEFAULT_TIMEOUT_SECONDS)\n\n            try:\n                (\n                    dtu_sn,\n                    single_phase_inverters,\n                    three_phase_inverters,\n                    ports,\n                    meters,\n                    hybrid_inverters,\n                    is_encrypted,\n                    enc_rand,\n                ) = await async_get_config_entry_data_for_host(host)\n            except CannotConnect:\n                errors[\"base\"] = \"cannot_connect\"\n            else:\n                await self.async_set_unique_id(dtu_sn)\n                self._abort_if_unique_id_configured()\n\n                return self.async_create_entry(\n                    title=host,\n                    data={\n                        CONF_HOST: host,\n                        CONF_UPDATE_INTERVAL: update_interval,\n                        CONF_DTU_SERIAL_NUMBER: dtu_sn,\n                        CONF_INVERTERS: single_phase_inverters,\n                        CONF_THREE_PHASE_INVERTERS: three_phase_inverters,\n                        CONF_PORTS: ports,\n                        CONF_METERS: meters,\n                        CONF_HYBRID_INVERTERS: hybrid_inverters,\n                        CONF_IS_ENCRYPTED: is_encrypted,\n                        CONF_ENC_RAND: enc_rand,\n                        CONF_TIMEOUT: timeout,\n                    },\n                )\n\n        return self.async_show_form(\n            step_id=\"user\", data_schema=DATA_SCHEMA, errors=errors\n        )\n\n    async def async_step_reconfigure(\n        self, user_input: dict[str, Any] | None = None\n    ) -> ConfigFlowResult:\n        \"\"\"Handle a reconfiguration flow initialized by the user.\"\"\"\n\n        entry = self.hass.config_entries.async_get_entry(self.context[\"entry_id\"])\n        assert entry is not None\n\n        errors = {}\n\n        if user_input is not None:\n            host = user_input[CONF_HOST]\n            update_interval = user_input.get(\n                CONF_UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL_SECONDS\n            )\n\n            timeout = user_input.get(CONF_TIMEOUT, DEFAULT_TIMEOUT_SECONDS)\n\n            try:\n                (\n                    dtu_sn,\n                    single_phase_inverters,\n                    three_phase_inverters,\n                    ports,\n                    meters,\n                    hybrid_inverters,\n                    is_encrypted,\n                    enc_rand,\n                ) = await async_get_config_entry_data_for_host(host)\n            except CannotConnect:\n                errors[\"base\"] = \"cannot_connect\"\n\n            else:\n                if dtu_sn != entry.unique_id:\n                    return self.async_abort(reason=\"another_device\")\n\n                data = {\n                    CONF_HOST: host,\n                    CONF_UPDATE_INTERVAL: update_interval,\n                    CONF_DTU_SERIAL_NUMBER: dtu_sn,\n                    CONF_INVERTERS: single_phase_inverters,\n                    CONF_THREE_PHASE_INVERTERS: three_phase_inverters,\n                    CONF_PORTS: ports,\n                    CONF_METERS: meters,\n                    CONF_HYBRID_INVERTERS: hybrid_inverters,\n                    CONF_IS_ENCRYPTED: is_encrypted,\n                    CONF_ENC_RAND: enc_rand,\n                    CONF_TIMEOUT: timeout,\n                }\n\n                self.hass.config_entries.async_update_entry(\n                    entry, data=data, version=CONFIG_VERSION\n                )\n                result = await self.hass.config_entries.async_reload(entry.entry_id)\n                if not result:\n                    errors[\"base\"] = \"unknown\"\n                else:\n                    return self.async_abort(reason=\"reconfigure_successful\")\n\n        return self.async_show_form(\n            step_id=\"reconfigure\",\n            data_schema=vol.Schema(\n                {\n                    vol.Required(CONF_HOST, default=entry.data[CONF_HOST]): str,\n                    vol.Optional(\n                        CONF_UPDATE_INTERVAL,\n                        default=entry.data[CONF_UPDATE_INTERVAL],\n                    ): vol.All(\n                        vol.Coerce(int),\n                        vol.Range(\n                            min=timedelta(seconds=MIN_UPDATE_INTERVAL_SECONDS).seconds\n                        ),\n                    ),\n                    vol.Optional(\n                        CONF_TIMEOUT,\n                        default=entry.data.get(CONF_TIMEOUT, DEFAULT_TIMEOUT_SECONDS),\n                    ): vol.All(\n                        vol.Coerce(int),\n                        vol.Range(min=timedelta(seconds=MIN_TIMEOUT_SECONDS).seconds),\n                    ),\n                }\n            ),\n            errors=errors,\n        )\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/const.py",
    "content": "\"\"\"Constants for the Hoymiles integration.\"\"\"\n\nDOMAIN = \"hoymiles_wifi\"\nNAME = \"Hoymiles\"\nDOMAIN = \"hoymiles_wifi\"\nDOMAIN_DATA = f\"{DOMAIN}_data\"\nCONFIG_VERSION = 5\n\nISSUE_URL = \"https://github.com/suaveolent/ha-hoymiles-wifi/issues\"\n\nCONF_UPDATE_INTERVAL = \"update_interval\"\nCONF_DTU_SERIAL_NUMBER = \"dtu_serial_number\"\nCONF_INVERTERS = \"inverters\"\nCONF_THREE_PHASE_INVERTERS = \"three_phase_inverters\"\nCONF_HYBRID_INVERTERS = \"hybrid_inverters\"\nCONF_PORTS = \"ports\"\nCONF_METERS = \"meters\"\nCONF_IS_ENCRYPTED = \"is_encrypted\"\nCONF_ENC_RAND = \"enc_rand\"\nCONF_TIMEOUT = \"timeout\"\n\nDEFAULT_UPDATE_INTERVAL_SECONDS = 35\nMIN_UPDATE_INTERVAL_SECONDS = 1\nDEFAULT_TIMEOUT_SECONDS = 10\nMIN_TIMEOUT_SECONDS = 1\n\nDEFAULT_CONFIG_UPDATE_INTERVAL_SECONDS = 60 * 5\nDEFAULT_APP_INFO_UPDATE_INTERVAL_SECONDS = 60 * 60 * 2\n\n\nHASS_DATA_COORDINATOR = \"data_coordinator\"\nHASS_CONFIG_COORDINATOR = \"config_coordinator\"\nHASS_APP_INFO_COORDINATOR = \"app_info_coordinator\"\nHASS_ENERGY_STORAGE_DATA_COORDINATOR = \"energy_stroage_data_coordinator\"\nHASS_DTU = \"dtu\"\nHASS_DATA_UNSUB_OPTIONS_UPDATE_LISTENER = \"unsub_options_update_listener\"\n\n\nFCTN_GENERATE_DTU_VERSION_STRING = \"generate_dtu_version_string\"\nFCTN_GENERATE_INVERTER_HW_VERSION_STRING = \"generate_version_string\"\nFCTN_GENERATE_INVERTER_SW_VERSION_STRING = \"generate_sw_version_string\"\n\nSTARTUP_MESSAGE = f\"\"\"\n\n-------------------------------------------------------------------\n{NAME}\nThis is a custom integration!\nIf you have any issues with it please open an issue here:\n{ISSUE_URL}\n-------------------------------------------------------------------\n\"\"\"\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/coordinator.py",
    "content": "\"\"\"Coordinator for Hoymiles integration.\"\"\"\n\nfrom datetime import timedelta\nimport logging\n\nimport homeassistant\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.const import CONF_HOST, Platform\nfrom homeassistant.helpers.update_coordinator import DataUpdateCoordinator\nfrom hoymiles_wifi.dtu import DTU\nfrom .util import is_encrypted_dtu, async_check_and_update_enc_rand\n\n\nfrom .const import DOMAIN\n\n_LOGGER = logging.getLogger(__name__)\n\nPLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.BINARY_SENSOR, Platform.BUTTON]\n\n\nclass HoymilesDataUpdateCoordinator(DataUpdateCoordinator):\n    \"\"\"Base data update coordinator for Hoymiles integration.\"\"\"\n\n    def __init__(\n        self,\n        hass: homeassistant,\n        dtu: DTU,\n        config_entry: ConfigEntry,\n        update_interval: timedelta,\n    ) -> None:\n        \"\"\"Initialize the HoymilesCoordinatorEntity.\"\"\"\n        self._dtu = dtu\n        self._hass = hass\n        self._config_entry = config_entry\n\n        _LOGGER.debug(\n            \"Setup entry with update interval %s. IP: %s\",\n            update_interval,\n            config_entry.data.get(CONF_HOST),\n        )\n\n        super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=update_interval)\n\n    def get_dtu(self) -> DTU:\n        \"\"\"Get the DTU object.\"\"\"\n        return self._dtu\n\n\nclass HoymilesRealDataUpdateCoordinator(HoymilesDataUpdateCoordinator):\n    \"\"\"Data coordinator for Hoymiles integration.\"\"\"\n\n    async def _async_update_data(self):\n        \"\"\"Update data via library.\"\"\"\n        _LOGGER.debug(\"Hoymiles data coordinator update\")\n\n        response = await self._dtu.async_get_real_data_new()\n\n        if not response:\n            _LOGGER.debug(\n                \"Unable to retrieve real data new. Inverter might be offline.\"\n            )\n        return response\n\n\nclass HoymilesConfigUpdateCoordinator(HoymilesDataUpdateCoordinator):\n    \"\"\"Config coordinator for Hoymiles integration.\"\"\"\n\n    async def _async_update_data(self):\n        \"\"\"Update data via library.\"\"\"\n        _LOGGER.debug(\"Hoymiles data coordinator update\")\n\n        response = await self._dtu.async_get_config()\n\n        if not response:\n            _LOGGER.debug(\"Unable to retrieve config data. Inverter might be offline.\")\n\n        return response\n\n\nclass HoymilesAppInfoUpdateCoordinator(HoymilesDataUpdateCoordinator):\n    \"\"\"App Info coordinator for Hoymiles integration.\"\"\"\n\n    async def _async_update_data(self):\n        \"\"\"Update data via library.\"\"\"\n        _LOGGER.debug(\"Hoymiles data coordinator update\")\n\n        response = await self._dtu.async_app_information_data()\n\n        if response and response.dtu_info.dfs:\n            if is_encrypted_dtu(response.dtu_info.dfs):\n                await async_check_and_update_enc_rand(\n                    self._hass,\n                    self._config_entry,\n                    self._dtu,\n                    response.dtu_info.enc_rand.hex(),\n                )\n\n        if not response:\n            _LOGGER.debug(\n                \"Unable to retrieve app information data. Inverter might be offline.\"\n            )\n        return response\n\n\nclass HoymilesGatewayInfoUpdateCoordinator(HoymilesDataUpdateCoordinator):\n    \"\"\"Gateway Info coordinator for Hoymiles integration.\"\"\"\n\n    async def _async_update_data(self):\n        \"\"\"Update data via library.\"\"\"\n        _LOGGER.debug(\"Hoymiles gateway info coordinator update\")\n\n        response = await self._dtu.async_get_gateway_info()\n\n        if not response:\n            _LOGGER.debug(\"Unable to retrieve gateway info. Inverter might be offline.\")\n        return response\n\n\nclass HoymilesGatewayNetworkInfoUpdateCoordinator(HoymilesDataUpdateCoordinator):\n    \"\"\"Gateway Network Info coordinator for Hoymiles integration.\"\"\"\n\n    async def _async_update_data(self):\n        \"\"\"Update data via library.\"\"\"\n        _LOGGER.debug(\"Hoymiles network info coordinator update\")\n\n        response = await self._dtu.async_get_gateway_network_info(\n            dtu_serial_number=int(self._dtu_serial_number)\n        )\n\n        if not response:\n            _LOGGER.debug(\n                \"Unable to retrieve network information. Inverter might be offline.\"\n            )\n        return response\n\n\nclass HoymilesEnergyStorageUpdateCoordinator(HoymilesDataUpdateCoordinator):\n    \"\"\"Energy Storage Update coordinator for Hoymiles integration.\"\"\"\n\n    def __init__(\n        self,\n        hass: homeassistant,\n        dtu: DTU,\n        config_entry: ConfigEntry,\n        update_interval: timedelta,\n        dtu_serial_number: int,\n        inverters: list[int],\n    ) -> None:\n        self._dtu_serial_number = dtu_serial_number\n        self._inverters = inverters\n        super().__init__(hass, dtu, config_entry, update_interval)\n\n    async def _async_update_data(self):\n        \"\"\"Update data via library.\"\"\"\n        _LOGGER.debug(\"Hoymiles energy storage coordinator update\")\n\n        responses = []\n\n        for inverter in self._inverters:\n            storage_data = await self._dtu.async_get_energy_storage_data(\n                dtu_serial_number=int(self._dtu_serial_number),\n                inverter_serial_number=inverter[\"inverter_serial_number\"],\n            )\n            if storage_data is not None:\n                responses.append(storage_data)\n\n        if not responses:\n            _LOGGER.debug(\n                \"Unable to retrieve energy storage data. Inverter might be offline.\"\n            )\n        return responses\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/entity.py",
    "content": "\"\"\"Entity base for Hoymiles entities.\"\"\"\n\nfrom dataclasses import dataclass\nimport logging\n\nfrom enum import Enum\n\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.helpers.device_registry import DeviceInfo\nfrom homeassistant.helpers.entity import Entity, EntityDescription\nfrom homeassistant.helpers.update_coordinator import CoordinatorEntity\nfrom hoymiles_wifi.hoymiles import (\n    DTUType,\n    get_dtu_model_name,\n    get_inverter_model_name,\n    get_meter_model_name,\n)\n\nfrom .const import CONF_DTU_SERIAL_NUMBER, DOMAIN\nfrom .coordinator import (\n    HoymilesDataUpdateCoordinator,\n)\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass DeviceType(Enum):\n    \"\"\"Device type.\"\"\"\n\n    ALL_DEVICES = 0\n    SINGLE_PHASE_METER = 1\n    THREE_PHASE_METER = 3\n\n\n@dataclass(frozen=True)\nclass HoymilesEntityDescription(EntityDescription):\n    \"\"\"Class to describe a Hoymiles Button entity.\"\"\"\n\n    is_dtu_sensor: bool = False\n    serial_number: str = None\n    port_number: int = None\n    supported_dtu_types: list[DTUType] = None\n    phase: str = None\n\n\nclass HoymilesEntity(Entity):\n    \"\"\"Base class for Hoymiles entities.\"\"\"\n\n    _attr_has_entity_name = True\n\n    def __init__(self, config_entry: ConfigEntry, description: EntityDescription):\n        \"\"\"Initialize the Hoymiles entity.\"\"\"\n        super().__init__()\n        self.entity_description = description\n        self._config_entry = config_entry\n        self._attr_unique_id = f\"hoymiles_{config_entry.entry_id}_{description.key}\"\n\n        if description.port_number:\n            self._attr_translation_placeholders = {\n                \"port_number\": f\"{description.port_number}\"\n            }\n        if description.phase:\n            self._attr_translation_placeholders = {\"phase\": f\"{description.phase}\"}\n\n        dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER]\n\n        serial_number = str(self.entity_description.serial_number)\n\n        if self.entity_description.is_dtu_sensor is True:\n            device_translation_key = \"dtu\"\n            device_model = get_dtu_model_name(self.entity_description.serial_number)\n        else:\n            if \"meter\" in self.entity_description.key:\n                device_model = get_meter_model_name(\n                    self.entity_description.serial_number\n                )\n                device_translation_key = \"meter\"\n            else:\n                if (\n                    hasattr(self.entity_description, \"model_name\")\n                    and self.entity_description.model_name\n                ):\n                    device_model = self.entity_description.model_name\n                    device_translation_key = \"hybrid_inverter\"\n                else:\n                    device_model = get_inverter_model_name(\n                        self.entity_description.serial_number\n                    )\n                    device_translation_key = \"inverter\"\n\n        device_info = DeviceInfo(\n            identifiers={(DOMAIN, serial_number)},\n            translation_key=device_translation_key,\n            manufacturer=\"Hoymiles\",\n            serial_number=serial_number.upper(),\n            model=device_model,\n        )\n\n        if not self.entity_description.is_dtu_sensor:\n            device_info[\"via_device\"] = (DOMAIN, dtu_serial_number)\n\n        self._attr_device_info = device_info\n\n\nclass HoymilesCoordinatorEntity(CoordinatorEntity, HoymilesEntity):\n    \"\"\"Represents a Hoymiles coordinator entity.\"\"\"\n\n    def __init__(\n        self,\n        config_entry: ConfigEntry,\n        description: EntityDescription,\n        coordinator: HoymilesDataUpdateCoordinator,\n    ):\n        \"\"\"Pass coordinator to CoordinatorEntity.\"\"\"\n        CoordinatorEntity.__init__(self, coordinator)\n        HoymilesEntity.__init__(self, config_entry, description)\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/error.py",
    "content": "\"\"\"Errors for hoymiles-wifi.\"\"\"\n\nfrom homeassistant.exceptions import HomeAssistantError\n\n\nclass CannotConnect(HomeAssistantError):\n    \"\"\"Error to indicate we cannot connect.\"\"\"\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/manifest.json",
    "content": "{\n  \"codeowners\": [\"@suaveolent\"],\n  \"config_flow\": true,\n  \"dependencies\": [],\n  \"documentation\": \"https://github.com/suaveolent/ha-hoymiles-wifi\",\n  \"issue_tracker\": \"https://github.com/suaveolent/ha-hoymiles-wifi/issues\",\n  \"domain\": \"hoymiles_wifi\",\n  \"iot_class\": \"local_polling\",\n  \"name\": \"Hoymiles\",\n  \"requirements\": [\"hoymiles-wifi==0.5.5\"],\n  \"version\": \"0.5.0\"\n}\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/number.py",
    "content": "\"\"\"Support for Hoymiles number sensors.\"\"\"\n\nimport dataclasses\nfrom dataclasses import dataclass\nfrom enum import Enum\nimport logging\n\nfrom homeassistant.components.number import (\n    NumberDeviceClass,\n    NumberEntity,\n    NumberEntityDescription,\n    NumberMode,\n)\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.core import HomeAssistant, callback\nfrom homeassistant.helpers.entity_platform import AddEntitiesCallback\n\nfrom .const import (\n    CONF_DTU_SERIAL_NUMBER,\n    CONF_INVERTERS,\n    CONF_THREE_PHASE_INVERTERS,\n    DOMAIN,\n    HASS_CONFIG_COORDINATOR,\n)\nfrom .entity import HoymilesCoordinatorEntity, HoymilesEntityDescription\n\nfrom hoymiles_wifi.hoymiles import DTUType, get_dtu_model_type\n\n\nclass SetAction(Enum):\n    \"\"\"Enum for set actions.\"\"\"\n\n    POWER_LIMIT = 1\n\n\n@dataclass(frozen=True)\nclass HoymilesNumberSensorEntityDescriptionMixin:\n    \"\"\"Mixin for required keys.\"\"\"\n\n\n@dataclass(frozen=True)\nclass HoymilesNumberSensorEntityDescription(\n    HoymilesEntityDescription, NumberEntityDescription\n):\n    \"\"\"Describes Hoymiles number sensor entity.\"\"\"\n\n    set_action: SetAction = None\n    conversion_factor: float = None\n    serial_number: str = None\n    is_dtu_sensor: bool = False\n\n\nCONFIG_CONTROL_ENTITIES = (\n    HoymilesNumberSensorEntityDescription(\n        key=\"limit_power_mypower\",\n        translation_key=\"limit_power_mypower\",\n        mode=NumberMode.SLIDER,\n        device_class=NumberDeviceClass.POWER_FACTOR,\n        set_action=SetAction.POWER_LIMIT,\n        conversion_factor=0.1,\n        is_dtu_sensor=True,\n    ),\n)\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_setup_entry(\n    hass: HomeAssistant,\n    config_entry: ConfigEntry,\n    async_add_entities: AddEntitiesCallback,\n) -> None:\n    \"\"\"Set up the Hoymiles number entities.\"\"\"\n    hass_data = hass.data[DOMAIN][config_entry.entry_id]\n    config_coordinator = hass_data.get(HASS_CONFIG_COORDINATOR, None)\n    single_phase_inverters = config_entry.data.get(CONF_INVERTERS, [])\n    three_phase_inverters = config_entry.data.get(CONF_THREE_PHASE_INVERTERS, [])\n    dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER]\n\n    if single_phase_inverters or three_phase_inverters:\n        sensors = []\n        for description in CONFIG_CONTROL_ENTITIES:\n            if description.is_dtu_sensor is True:\n                updated_description = dataclasses.replace(\n                    description, serial_number=dtu_serial_number\n                )\n                sensors.append(\n                    HoymilesNumberEntity(\n                        config_entry, updated_description, config_coordinator\n                    )\n                )\n        async_add_entities(sensors)\n\n\nclass HoymilesNumberEntity(HoymilesCoordinatorEntity, NumberEntity):\n    \"\"\"Hoymiles Number entity.\"\"\"\n\n    def __init__(\n        self,\n        config_entry: ConfigEntry,\n        description: HoymilesNumberSensorEntityDescription,\n        coordinator: HoymilesCoordinatorEntity,\n    ) -> None:\n        \"\"\"Initialize the HoymilesNumberEntity.\"\"\"\n        super().__init__(config_entry, description, coordinator)\n        self._attribute_name = description.key\n        self._conversion_factor = description.conversion_factor\n        self._set_action = description.set_action\n        self._native_value = None\n        self._assumed_state = False\n\n        self.update_state_value()\n\n    @callback\n    def _handle_coordinator_update(self) -> None:\n        \"\"\"Handle updated data from the coordinator.\"\"\"\n        self.update_state_value()\n        super()._handle_coordinator_update()\n\n    @property\n    def native_value(self) -> float:\n        \"\"\"Get the native value of the entity.\"\"\"\n        return self._native_value\n\n    @property\n    def assumed_state(self):\n        \"\"\"Return the assumed state of the entity.\"\"\"\n        return self._assumed_state\n\n    async def async_set_native_value(self, value: float) -> None:\n        \"\"\"Set the native value of the entity.\n\n        Args:\n            value (float): The value to set.\n        \"\"\"\n        if self._set_action == SetAction.POWER_LIMIT:\n            dtu = self.coordinator.get_dtu()\n            if value < 0 and value > 100:\n                _LOGGER.error(\"Power limit value out of range\")\n                return\n            await dtu.async_set_power_limit(value)\n            await self.coordinator.async_request_refresh()\n        else:\n            _LOGGER.error(\"Invalid set action!\")\n            return\n\n        self._assumed_state = True\n        self._native_value = value\n\n    def update_state_value(self):\n        \"\"\"Update the state value of the entity.\"\"\"\n\n        # For the moment, we can only retrive the power limit\n        self._native_value = getattr(\n            self.coordinator.data,\n            self._attribute_name,\n            None,\n        )\n\n        self._assumed_state = False\n\n        if self._native_value is not None and self._conversion_factor is not None:\n            self._native_value *= self._conversion_factor\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/sensor.py",
    "content": "\"\"\"Support for Hoymiles sensors.\"\"\"\n\nimport dataclasses\nfrom dataclasses import dataclass\nfrom datetime import datetime, time, timedelta\nfrom enum import Enum\nimport logging\nimport re\n\nfrom homeassistant.components.sensor import (\n    RestoreSensor,\n    SensorDeviceClass,\n    SensorEntity,\n    SensorEntityDescription,\n    SensorStateClass,\n)\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.const import (\n    PERCENTAGE,\n    EntityCategory,\n    UnitOfElectricCurrent,\n    UnitOfElectricPotential,\n    UnitOfEnergy,\n    UnitOfFrequency,\n    UnitOfPower,\n    UnitOfTemperature,\n    UnitOfReactivePower,\n)\nfrom homeassistant.core import HomeAssistant, callback\nfrom homeassistant.helpers.entity_platform import AddEntitiesCallback\nimport hoymiles_wifi.hoymiles\nfrom hoymiles_wifi.hoymiles import DTUType, get_dtu_model_type\n\nfrom .const import (\n    CONF_DTU_SERIAL_NUMBER,\n    CONF_INVERTERS,\n    CONF_HYBRID_INVERTERS,\n    CONF_METERS,\n    CONF_PORTS,\n    CONF_THREE_PHASE_INVERTERS,\n    DOMAIN,\n    FCTN_GENERATE_DTU_VERSION_STRING,\n    FCTN_GENERATE_INVERTER_HW_VERSION_STRING,\n    FCTN_GENERATE_INVERTER_SW_VERSION_STRING,\n    HASS_APP_INFO_COORDINATOR,\n    HASS_CONFIG_COORDINATOR,\n    HASS_DATA_COORDINATOR,\n    HASS_ENERGY_STORAGE_DATA_COORDINATOR,\n)\nfrom .entity import (\n    HoymilesCoordinatorEntity,\n    HoymilesEntityDescription,\n    DeviceType,\n)\n\n_LOGGER = logging.getLogger(__name__)\n\n\nclass ConversionAction(Enum):\n    \"\"\"Enumeration for conversion actions.\"\"\"\n\n    HEX = 1\n\n\n@dataclass(frozen=True)\nclass HoymilesSensorEntityDescriptionMixin:\n    \"\"\"Mixin for required keys.\"\"\"\n\n\n@dataclass(frozen=True)\nclass HoymilesSensorEntityDescription(\n    HoymilesEntityDescription, SensorEntityDescription\n):\n    \"\"\"Describes Hoymiles data sensor entity.\"\"\"\n\n    conversion_factor: float = None\n    reset_at_midnight: bool = False\n    version_translation_function: str = None\n    version_prefix: str = None\n    assume_state: bool = False\n    requires_device_type: int = DeviceType.ALL_DEVICES\n    force_keep_maximum_within_day: bool = False\n\n\n@dataclass(frozen=True)\nclass HoymilesEnergyStorageSensorEntityDescription(\n    HoymilesEntityDescription, SensorEntityDescription\n):\n    \"\"\"Describes Hoymiles energy storage data sensor entity.\"\"\"\n\n    model_name: str = None\n    conversion_factor: float = None\n    reset_at_midnight: bool = False\n    version_translation_function: str = None\n    version_prefix: str = None\n    assume_state: bool = False\n    force_keep_maximum_within_day: bool = False\n\n\n@dataclass(frozen=True)\nclass HoymilesDiagnosticEntityDescription(\n    HoymilesEntityDescription, SensorEntityDescription\n):\n    \"\"\"Describes Hoymiles diagnostic sensor entity.\"\"\"\n\n    conversion: ConversionAction = None\n    separator: str = None\n\n\nHOYMILES_SENSORS = [\n    HoymilesSensorEntityDescription(\n        key=\"dtu_power\",\n        translation_key=\"ac_active_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n        is_dtu_sensor=True,\n        supported_dtu_types=[\n            DTUType.DTU_G100,\n            DTUType.DTU_W100,\n            DTUType.DTU_LITE_S,\n            DTUType.DTU_LITE,\n            DTUType.DTU_PRO,\n            DTUType.DTU_PRO_S,\n            DTUType.DTUBI,\n            DTUType.DTU_W_LITE,\n        ],\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"dtu_daily_energy\",\n        translation_key=\"ac_daily_energy\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        reset_at_midnight=True,\n        force_keep_maximum_within_day=True,\n        is_dtu_sensor=True,\n        supported_dtu_types=[\n            DTUType.DTU_G100,\n            DTUType.DTU_W100,\n            DTUType.DTU_LITE_S,\n            DTUType.DTU_LITE,\n            DTUType.DTU_PRO,\n            DTUType.DTU_PRO_S,\n            DTUType.DTUBI,\n            DTUType.DTU_W_LITE,\n        ],\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"sgs_data[<inverter_count>].active_power\",\n        translation_key=\"ac_active_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"sgs_data[<inverter_count>].reactive_power\",\n        translation_key=\"ac_reactive_power\",\n        native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,\n        device_class=SensorDeviceClass.REACTIVE_POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"sgs_data[<inverter_count>].voltage\",\n        translation_key=\"grid_voltage\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"sgs_data[<inverter_count>].current\",\n        translation_key=\"ac_current\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"sgs_data[<inverter_count>].frequency\",\n        translation_key=\"grid_frequency\",\n        native_unit_of_measurement=UnitOfFrequency.HERTZ,\n        device_class=SensorDeviceClass.FREQUENCY,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"sgs_data[<inverter_count>].power_factor\",\n        translation_key=\"inverter_power_factor\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=SensorDeviceClass.POWER_FACTOR,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"sgs_data[<inverter_count>].temperature\",\n        translation_key=\"inverter_temperature\",\n        native_unit_of_measurement=UnitOfTemperature.CELSIUS,\n        device_class=SensorDeviceClass.TEMPERATURE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"sgs_data[<inverter_count>].warning_number\",\n        translation_key=\"inverter_warning_number\",\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].active_power\",\n        translation_key=\"ac_active_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].reactive_power\",\n        translation_key=\"ac_reactive_power\",\n        native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,\n        device_class=SensorDeviceClass.REACTIVE_POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].voltage_phase_A\",\n        translation_key=\"voltage_phase_A\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].voltage_phase_B\",\n        translation_key=\"voltage_phase_B\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].voltage_phase_C\",\n        translation_key=\"voltage_phase_C\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].voltage_line_AB\",\n        translation_key=\"voltage_line_AB\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].voltage_line_BC\",\n        translation_key=\"voltage_line_BC\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].voltage_line_CA\",\n        translation_key=\"voltage_line_CA\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].frequency\",\n        translation_key=\"grid_frequency\",\n        native_unit_of_measurement=UnitOfFrequency.HERTZ,\n        device_class=SensorDeviceClass.FREQUENCY,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>.current_phase_A\",\n        translation_key=\"current_phase_A\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>.current_phase_B\",\n        translation_key=\"current_phase_B\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>.current_phase_C\",\n        translation_key=\"current_phase_C\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].power_factor\",\n        translation_key=\"inverter_power_factor\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=SensorDeviceClass.POWER_FACTOR,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].temperature\",\n        translation_key=\"inverter_temperature\",\n        native_unit_of_measurement=UnitOfTemperature.CELSIUS,\n        device_class=SensorDeviceClass.TEMPERATURE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"tgs_data[<inverter_count>].warning_number\",\n        translation_key=\"inverter_warning_number\",\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"pv_data[<pv_count>].voltage\",\n        translation_key=\"port_dc_voltage\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"pv_data[<pv_count>].current\",\n        translation_key=\"port_dc_current\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"pv_data[<pv_count>].power\",\n        translation_key=\"port_dc_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"pv_data[<pv_count>].energy_total\",\n        translation_key=\"port_dc_total_energy\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"pv_data[<pv_count>].energy_daily\",\n        translation_key=\"port_dc_daily_energy\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        reset_at_midnight=True,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"pv_data[<pv_count>].error_code\",\n        translation_key=\"port_error_code\",\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].phase_total_power\",\n        translation_key=\"phase_total_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=10,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].phase_A_power\",\n        translation_key=\"phase_A_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=10,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].phase_B_power\",\n        translation_key=\"phase_B_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=10,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].phase_C_power\",\n        translation_key=\"phase_C_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=10,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].power_factor_total\",\n        translation_key=\"power_factor_total\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=SensorDeviceClass.POWER_FACTOR,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].energy_total_power\",\n        translation_key=\"energy_total_power\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=10.0,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].energy_phase_A\",\n        translation_key=\"energy_phase_A\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=10.0,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].energy_phase_B\",\n        translation_key=\"energy_phase_B\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n        conversion_factor=10.0,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].energy_phase_C\",\n        translation_key=\"energy_phase_C\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n        conversion_factor=10.0,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].energy_total_consumed\",\n        translation_key=\"energy_total_consumed\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=10.0,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].energy_phase_A_consumed\",\n        translation_key=\"energy_phase_A_consumed\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=10.0,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].energy_phase_B_consumed\",\n        translation_key=\"energy_phase_B_consumed\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n        conversion_factor=10.0,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].energy_phase_C_consumed\",\n        translation_key=\"energy_phase_C_consumed\",\n        native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n        conversion_factor=10.0,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].voltage_phase_A\",\n        translation_key=\"voltage_phase_A\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].voltage_phase_B\",\n        translation_key=\"voltage_phase_B\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].voltage_phase_C\",\n        translation_key=\"voltage_phase_C\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].current_phase_A\",\n        translation_key=\"current_phase_A\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].current_phase_B\",\n        translation_key=\"current_phase_B\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].current_phase_C\",\n        translation_key=\"current_phase_C\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].power_factor_phase_A\",\n        translation_key=\"power_factor_phase_A\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=SensorDeviceClass.POWER_FACTOR,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].power_factor_phase_B\",\n        translation_key=\"power_factor_phase_B\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=SensorDeviceClass.POWER_FACTOR,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"meter_data[<meter_count>].power_factor_phase_C\",\n        translation_key=\"power_factor_phase_C\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=SensorDeviceClass.POWER_FACTOR,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n        requires_device_type=DeviceType.THREE_PHASE_METER,\n    ),\n]\n\nCONFIG_DIAGNOSTIC_SENSORS = [\n    HoymilesDiagnosticEntityDescription(\n        key=\"wifi_ssid\",\n        translation_key=\"wifi_ssid\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        icon=\"mdi:wifi\",\n        is_dtu_sensor=True,\n    ),\n    HoymilesDiagnosticEntityDescription(\n        key=\"meter_kind\",\n        translation_key=\"meter_kind\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        is_dtu_sensor=True,\n    ),\n    HoymilesDiagnosticEntityDescription(\n        key=\"wifi_mac_[0-5]\",\n        translation_key=\"mac_address\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        separator=\":\",\n        conversion=ConversionAction.HEX,\n        is_dtu_sensor=True,\n    ),\n    HoymilesDiagnosticEntityDescription(\n        key=\"wifi_ip_addr_[0-3]\",\n        translation_key=\"ip_address\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        separator=\".\",\n        is_dtu_sensor=True,\n    ),\n    HoymilesDiagnosticEntityDescription(\n        key=\"dtu_ap_ssid\",\n        translation_key=\"dtu_ap_ssid\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        icon=\"mdi:access-point\",\n        is_dtu_sensor=True,\n    ),\n]\n\nAPP_INFO_SENSORS: tuple[HoymilesSensorEntityDescription, ...] = (\n    HoymilesSensorEntityDescription(\n        key=\"dtu_info.dtu_sw_version\",\n        translation_key=\"dtu_sw_version\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        version_translation_function=FCTN_GENERATE_DTU_VERSION_STRING,\n        version_prefix=\"V\",\n        is_dtu_sensor=True,\n        assume_state=True,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"dtu_info.dtu_hw_version\",\n        translation_key=\"dtu_hw_version\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        version_translation_function=FCTN_GENERATE_DTU_VERSION_STRING,\n        version_prefix=\"H\",\n        is_dtu_sensor=True,\n        assume_state=True,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"pv_info[<inverter_count>].pv_sw_version\",\n        translation_key=\"pv_sw_version\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        version_translation_function=FCTN_GENERATE_INVERTER_SW_VERSION_STRING,\n        version_prefix=\"V\",\n        assume_state=True,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"pv_info[<inverter_count>].pv_hw_version\",\n        translation_key=\"pv_hw_version\",\n        entity_category=EntityCategory.DIAGNOSTIC,\n        version_translation_function=FCTN_GENERATE_INVERTER_HW_VERSION_STRING,\n        version_prefix=\"H\",\n        assume_state=True,\n    ),\n    HoymilesSensorEntityDescription(\n        key=\"dtu_info.signal_strength\",\n        translation_key=\"signal_strength\",\n        native_unit_of_measurement=PERCENTAGE,\n        entity_category=EntityCategory.DIAGNOSTIC,\n        icon=\"mdi:wifi\",\n        is_dtu_sensor=True,\n    ),\n)\n\nHOYMILES_ENERGY_STORAGE_SENSORS = [\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].production.energy_to_load\",\n        translation_key=\"energy_to_load\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].production.energy_to_battery\",\n        translation_key=\"energy_to_battery\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].production.energy_to_grid\",\n        translation_key=\"energy_to_grid\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].consumption.energy_from_pv\",\n        translation_key=\"energy_from_pv\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].consumption.energy_from_battery\",\n        translation_key=\"energy_from_battery\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].consumption.energy_from_grid\",\n        translation_key=\"energy_from_grid\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_panels[<pv_panel_count>].voltage\",\n        translation_key=\"pv_panel_voltage\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_panels[<pv_panel_count>].current\",\n        translation_key=\"pv_panel_current\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_panels[<pv_panel_count>].power\",\n        translation_key=\"pv_panel_power\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_panels[<pv_panel_count>].energy\",\n        translation_key=\"pv_panel_energy\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.state_of_charge\",\n        translation_key=\"state_of_charge\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=SensorDeviceClass.BATTERY,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.state_of_health\",\n        translation_key=\"state_of_health\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=SensorDeviceClass.BATTERY,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.voltage\",\n        translation_key=\"battery_voltage\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.internal_charge_mode\",\n        translation_key=\"internal_charge_mode\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.internal_discharge_mode\",\n        translation_key=\"internal_discharge_mode\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.cell_voltage_high\",\n        translation_key=\"cell_voltage_high\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.cell_voltage_low\",\n        translation_key=\"cell_voltage_low\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.temp_high_charge\",\n        translation_key=\"temp_high_charge\",\n        native_unit_of_measurement=UnitOfTemperature.CELSIUS,\n        device_class=SensorDeviceClass.TEMPERATURE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.temp_low_charge\",\n        translation_key=\"temp_low_charge\",\n        native_unit_of_measurement=UnitOfTemperature.CELSIUS,\n        device_class=SensorDeviceClass.TEMPERATURE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.temp_high_module\",\n        translation_key=\"temp_high_module\",\n        native_unit_of_measurement=UnitOfTemperature.CELSIUS,\n        device_class=SensorDeviceClass.TEMPERATURE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.temp_low_module\",\n        translation_key=\"temp_low_module\",\n        native_unit_of_measurement=UnitOfTemperature.CELSIUS,\n        device_class=SensorDeviceClass.TEMPERATURE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.energy_charged\",\n        translation_key=\"energy_charged\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.energy_discharged\",\n        translation_key=\"energy_discharged\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.voltage_charge_high\",\n        translation_key=\"voltage_charge_high\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.001,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.voltage_charge_low\",\n        translation_key=\"voltage_charge_low\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.001,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.voltage_module_high\",\n        translation_key=\"voltage_module_high\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].battery_management.voltage_module_low\",\n        translation_key=\"voltage_module_low\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].grid.param.frequency\",\n        translation_key=\"grid_frequency\",\n        native_unit_of_measurement=UnitOfFrequency.HERTZ,\n        device_class=SensorDeviceClass.FREQUENCY,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].grid.phases[<phase_count>].voltage\",\n        translation_key=\"grid_voltage_phase\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].grid.phases[<phase_count>].current\",\n        translation_key=\"grid_current_phase\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].grid.phases[<phase_count>].active_power\",\n        translation_key=\"grid_active_power_phase\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].grid.phases[<phase_count>].reactive_power\",\n        translation_key=\"grid_reactive_power_phase\",\n        native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,\n        device_class=SensorDeviceClass.REACTIVE_POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].grid.phases[<phase_count>].power_factor\",\n        translation_key=\"grid_power_factor_phase\",\n        native_unit_of_measurement=PERCENTAGE,\n        device_class=None,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].grid.phases[<phase_count>].energy_frequency\",\n        translation_key=\"grid_energy_frequency_phase\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].grid.phases[<phase_count>].energy_consumed\",\n        translation_key=\"grid_energy_consumed_phase\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].load.param.status\",\n        translation_key=\"load_status\",\n        device_class=SensorDeviceClass.ENUM,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].load.param.frequency\",\n        translation_key=\"load_frequency\",\n        native_unit_of_measurement=UnitOfFrequency.HERTZ,\n        device_class=SensorDeviceClass.FREQUENCY,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].load.phases[<phase_count>].voltage\",\n        translation_key=\"load_voltage_phase\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].load.phases[<phase_count>].active_power\",\n        translation_key=\"load_active_power_phase\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].load.phases[<phase_count>].energy_consumed\",\n        translation_key=\"load_energy_consumed_phase\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.param.status\",\n        translation_key=\"inverter_status\",\n        device_class=SensorDeviceClass.ENUM,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.param.frequency\",\n        translation_key=\"inverter_frequency\",\n        native_unit_of_measurement=UnitOfFrequency.HERTZ,\n        device_class=SensorDeviceClass.FREQUENCY,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.param.isolation_resistance\",\n        translation_key=\"inverter_isolation_resistance\",\n        native_unit_of_measurement=\"kΩ\",\n        device_class=None,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.param.leakage_current\",\n        translation_key=\"inverter_leakage_current\",\n        native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.param.drm_signal\",\n        translation_key=\"inverter_drm_signal\",\n        device_class=SensorDeviceClass.ENUM,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].voltage\",\n        translation_key=\"inverter_voltage_phase\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].current\",\n        translation_key=\"inverter_current_phase\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].active_power\",\n        translation_key=\"inverter_active_power_phase\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].reactive_power\",\n        translation_key=\"inverter_reactive_power_phase\",\n        native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,\n        device_class=SensorDeviceClass.REACTIVE_POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].dc_current\",\n        translation_key=\"inverter_dc_current_phase\",\n        native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].dc_voltage\",\n        translation_key=\"inverter_dc_voltage_phase\",\n        native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].eps_voltage\",\n        translation_key=\"inverter_eps_voltage_phase\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].eps_current\",\n        translation_key=\"inverter_eps_current_phase\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].inverter.phases[<phase_count>].eps_power\",\n        translation_key=\"inverter_eps_power_phase\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_inverter.param.status\",\n        translation_key=\"pv_inverter_status\",\n        device_class=SensorDeviceClass.ENUM,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_inverter.param.frequency\",\n        translation_key=\"pv_inverter_frequency\",\n        native_unit_of_measurement=UnitOfFrequency.HERTZ,\n        device_class=SensorDeviceClass.FREQUENCY,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_inverter.phases[<phase_count>].voltage\",\n        translation_key=\"pv_inverter_voltage_phase\",\n        native_unit_of_measurement=UnitOfElectricPotential.VOLT,\n        device_class=SensorDeviceClass.VOLTAGE,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_inverter.phases[<phase_count>].current\",\n        translation_key=\"pv_inverter_current_phase\",\n        native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,\n        device_class=SensorDeviceClass.CURRENT,\n        state_class=SensorStateClass.MEASUREMENT,\n        conversion_factor=0.01,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_inverter.phases[<phase_count>].active_power\",\n        translation_key=\"pv_inverter_active_power_phase\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_inverter.phases[<phase_count>].reactive_power\",\n        translation_key=\"pv_inverter_reactive_power_phase\",\n        native_unit_of_measurement=UnitOfReactivePower.VOLT_AMPERE_REACTIVE,\n        device_class=SensorDeviceClass.REACTIVE_POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].pv_inverter.phases[<phase_count>].energy\",\n        translation_key=\"pv_inverter_energy_phase\",\n        native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,\n        device_class=SensorDeviceClass.ENERGY,\n        state_class=SensorStateClass.TOTAL_INCREASING,\n        conversion_factor=0.1,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].power_flow.pv_to_load\",\n        translation_key=\"pv_to_load\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].power_flow.pv_to_battery\",\n        translation_key=\"pv_to_battery\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].power_flow.pv_to_grid\",\n        translation_key=\"pv_to_grid\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].power_flow.battery_to_load\",\n        translation_key=\"battery_to_load\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].power_flow.grid_to_load\",\n        translation_key=\"grid_to_load\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n    HoymilesEnergyStorageSensorEntityDescription(\n        key=\"[<inverter_count>].power_flow.battery_to_grid\",\n        translation_key=\"battery_to_grid\",\n        native_unit_of_measurement=UnitOfPower.WATT,\n        device_class=SensorDeviceClass.POWER,\n        state_class=SensorStateClass.MEASUREMENT,\n    ),\n]\n\n\nasync def async_setup_entry(\n    hass: HomeAssistant,\n    config_entry: ConfigEntry,\n    async_add_entities: AddEntitiesCallback,\n) -> None:\n    \"\"\"Set up sensor platform.\"\"\"\n\n    hass_data = hass.data[DOMAIN][config_entry.entry_id]\n    data_coordinator = hass_data.get(HASS_DATA_COORDINATOR, None)\n    config_coordinator = hass_data.get(HASS_CONFIG_COORDINATOR, None)\n    app_info_coordinator = hass_data.get(HASS_APP_INFO_COORDINATOR, None)\n    energy_storage_data_coordinator = hass_data.get(\n        HASS_ENERGY_STORAGE_DATA_COORDINATOR, None\n    )\n    dtu_serial_number = config_entry.data[CONF_DTU_SERIAL_NUMBER]\n    single_phase_inverters = config_entry.data.get(CONF_INVERTERS, [])\n    three_phase_inverters = config_entry.data.get(CONF_THREE_PHASE_INVERTERS, [])\n    hybrid_inverters = config_entry.data.get(CONF_HYBRID_INVERTERS, [])\n    meters = config_entry.data.get(CONF_METERS, [])\n    inverters = single_phase_inverters + three_phase_inverters\n    ports = config_entry.data[CONF_PORTS]\n    sensors = []\n\n    # Real Data Sensors\n\n    if inverters:\n        for description in HOYMILES_SENSORS:\n            device_class = description.device_class\n            if device_class == SensorDeviceClass.ENERGY:\n                class_name = HoymilesEnergySensorEntity\n            else:\n                class_name = HoymilesDataSensorEntity\n\n            if \"sgs_data\" in description.key and single_phase_inverters:\n                sensor_entities = get_sensors_for_description(\n                    config_entry,\n                    description,\n                    data_coordinator,\n                    class_name,\n                    dtu_serial_number,\n                    single_phase_inverters,\n                    [],\n                )\n                sensors.extend(sensor_entities)\n\n            elif \"tgs_data\" in description.key and three_phase_inverters:\n                sensor_entities = get_sensors_for_description(\n                    config_entry,\n                    description,\n                    data_coordinator,\n                    class_name,\n                    dtu_serial_number,\n                    three_phase_inverters,\n                    [],\n                )\n                sensors.extend(sensor_entities)\n            elif \"meter\" in description.key and meters:\n                sensor_entities = get_sensors_for_description(\n                    config_entry,\n                    description,\n                    data_coordinator,\n                    class_name,\n                    dtu_serial_number,\n                    [],\n                    [],\n                    meters,\n                )\n                sensors.extend(sensor_entities)\n\n            else:\n                sensor_entities = get_sensors_for_description(\n                    config_entry,\n                    description,\n                    data_coordinator,\n                    class_name,\n                    dtu_serial_number,\n                    [],\n                    ports,\n                )\n                sensors.extend(sensor_entities)\n\n        for description in CONFIG_DIAGNOSTIC_SENSORS:\n            sensor_entities = get_sensors_for_description(\n                config_entry,\n                description,\n                config_coordinator,\n                HoymilesDiagnosticSensorEntity,\n                dtu_serial_number,\n                inverters,\n                ports,\n            )\n            sensors.extend(sensor_entities)\n\n        for description in APP_INFO_SENSORS:\n            sensor_entities = get_sensors_for_description(\n                config_entry,\n                description,\n                app_info_coordinator,\n                HoymilesDataSensorEntity,\n                dtu_serial_number,\n                inverters,\n                ports,\n            )\n            sensors.extend(sensor_entities)\n\n    if hybrid_inverters:\n        for description in HOYMILES_ENERGY_STORAGE_SENSORS:\n            sensor_entities = get_sensors_for_hybrid_inverter_description(\n                config_entry,\n                description,\n                energy_storage_data_coordinator,\n                HoymilesEnergyStorageSensorEntity,\n                dtu_serial_number,\n                hybrid_inverters,\n            )\n            sensors.extend(sensor_entities)\n\n    async_add_entities(sensors)\n\n\ndef get_sensors_for_description(\n    config_entry: ConfigEntry,\n    description: SensorEntityDescription,\n    coordinator: HoymilesCoordinatorEntity,\n    class_name: SensorEntity,\n    dtu_serial_number: str,\n    inverters: list,\n    ports: list,\n    meters: list = [],\n) -> list[SensorEntity]:\n    \"\"\"Get sensors for the given description.\"\"\"\n\n    sensors = []\n\n    if \"<inverter_count>\" in description.key:\n        for index, inverter_serial in enumerate(inverters):\n            new_key = description.key.replace(\"<inverter_count>\", str(index))\n            updated_description = dataclasses.replace(\n                description, key=new_key, serial_number=inverter_serial\n            )\n            sensor = class_name(config_entry, updated_description, coordinator)\n            sensors.append(sensor)\n    elif \"<pv_count>\" in description.key:\n        for index, port in enumerate(ports):\n            inverter_serial = port[\"inverter_serial_number\"]\n            port_number = port[\"port_number\"]\n            new_key = str(description.key).replace(\"<pv_count>\", str(index))\n            updated_description = dataclasses.replace(\n                description,\n                key=new_key,\n                serial_number=inverter_serial,\n                port_number=port_number,\n            )\n            sensor = class_name(config_entry, updated_description, coordinator)\n            sensors.append(sensor)\n    elif \"meter_count\" in description.key:\n        for index, meter in enumerate(meters):\n            meter_serial = meter[\"meter_serial_number\"]\n            meter_type = meter[\"device_type\"]\n\n            if description.requires_device_type.value in (\n                DeviceType.ALL_DEVICES.value,\n                meter_type,\n            ):\n                new_key = description.key.replace(\"<meter_count>\", str(index))\n                updated_description = dataclasses.replace(\n                    description, key=new_key, serial_number=meter_serial\n                )\n                sensor = class_name(config_entry, updated_description, coordinator)\n                sensors.append(sensor)\n    else:\n        if description.supported_dtu_types is not None:\n            serial_bytes = bytes.fromhex(dtu_serial_number)\n\n            dtu_type = None\n            try:\n                dtu_type = get_dtu_model_type(serial_bytes)\n            except ValueError as e:\n                _LOGGER.error(f\"Error getting DTU model type: {e}\")\n\n        if (\n            description.supported_dtu_types is None\n            or dtu_type in description.supported_dtu_types\n        ):\n            updated_description = dataclasses.replace(\n                description, serial_number=dtu_serial_number\n            )\n            sensor = class_name(config_entry, updated_description, coordinator)\n            sensors.append(sensor)\n\n    return sensors\n\n\ndef get_sensors_for_hybrid_inverter_description(\n    config_entry: ConfigEntry,\n    description: SensorEntityDescription,\n    coordinator: HoymilesCoordinatorEntity,\n    class_name: SensorEntity,\n    dtu_serial_number: str,\n    inverters: list,\n) -> list[SensorEntity]:\n    \"\"\"Get sensors for the given description.\"\"\"\n\n    sensors = []\n\n    if \"<inverter_count>\" in description.key:\n        for index, inverter in enumerate(inverters):\n            new_key = description.key.replace(\"<inverter_count>\", str(index))\n\n            if \"<pv_panel_count>\" in description.key:\n                # TODO: Dynamically determine number of PV panels\n                for pv_index in range(0, 2):\n                    new_pv_index_key = new_key.replace(\n                        \"<pv_panel_count>\", str(pv_index)\n                    )\n                    updated_description = dataclasses.replace(\n                        description,\n                        key=new_pv_index_key,\n                        serial_number=inverter[\"inverter_serial_number\"],\n                        model_name=inverter[\"model_name\"],\n                        port_number=pv_index + 1,\n                    )\n                    sensor = class_name(config_entry, updated_description, coordinator)\n                    sensors.append(sensor)\n            elif \"<phase_count>\" in description.key:\n                # TODO: Dynamically determine number of phases\n                for phase_index in range(0, 3):\n                    new_phase_index_key = new_key.replace(\n                        \"<phase_count>\", str(phase_index)\n                    )\n                    updated_description = dataclasses.replace(\n                        description,\n                        key=new_phase_index_key,\n                        serial_number=inverter[\"inverter_serial_number\"],\n                        model_name=inverter[\"model_name\"],\n                        phase=[\"A\", \"B\", \"C\"][phase_index],\n                    )\n                    sensor = class_name(config_entry, updated_description, coordinator)\n                    sensors.append(sensor)\n\n            else:\n                updated_description = dataclasses.replace(\n                    description,\n                    key=new_key,\n                    serial_number=inverter[\"inverter_serial_number\"],\n                    model_name=inverter[\"model_name\"],\n                )\n                sensor = class_name(config_entry, updated_description, coordinator)\n                sensors.append(sensor)\n\n    else:\n        updated_description = dataclasses.replace(\n            description, serial_number=dtu_serial_number\n        )\n        sensor = class_name(config_entry, updated_description, coordinator)\n        sensors.append(sensor)\n\n    return sensors\n\n\nclass HoymilesDataSensorEntity(HoymilesCoordinatorEntity, RestoreSensor):\n    \"\"\"Represents a sensor entity for Hoymiles data.\"\"\"\n\n    def __init__(\n        self,\n        config_entry: ConfigEntry,\n        description: HoymilesSensorEntityDescription,\n        coordinator: HoymilesCoordinatorEntity,\n    ):\n        \"\"\"Pass coordinator to CoordinatorEntity.\"\"\"\n        super().__init__(config_entry, description, coordinator)\n\n        self._attribute_name = description.key\n        self._conversion_factor = description.conversion_factor\n        self._version_translation_function = description.version_translation_function\n        self._version_prefix = description.version_prefix\n        self._native_value = None\n        self._assumed_state = False\n        self._last_known_value = None\n        self._last_successful_update = None\n        self._last_update_state = None\n\n        self.update_state_value()\n\n    @callback\n    def _handle_coordinator_update(self) -> None:\n        \"\"\"Handle updated data from the coordinator.\"\"\"\n        self.update_state_value()\n        super()._handle_coordinator_update()\n\n    @property\n    def native_value(self):\n        \"\"\"Return the native value of the sensor.\"\"\"\n        if self._native_value == 0.0:\n            if self.entity_description.assume_state:\n                return self._last_known_value\n            elif (\n                self._last_successful_update is not None\n                and datetime.now() - self._last_successful_update\n                <= timedelta(minutes=3)\n            ):\n                _LOGGER.debug(\n                    \"[%s] Returning last known value: %s, instead of 0.0 to cope with inverter in offline mode.\",\n                    self.name,\n                    self._last_known_value,\n                )\n                self._assumed_state = True\n                return self._last_known_value\n        else:\n            self._last_successful_update = datetime.now()\n            self._last_known_value = self._native_value\n        self._assumed_state = False\n        return self._native_value\n\n    @property\n    def assumed_state(self):\n        \"\"\"Return the assumed state of the sensor.\"\"\"\n        return self._assumed_state\n\n    def update_state_value(self):\n        \"\"\"Update the state value of the sensor based on the coordinator data.\"\"\"\n        new_native_value = 0.0\n\n        if self.coordinator is not None and (\n            not hasattr(self.coordinator, \"data\") or self.coordinator.data is None\n        ):\n            new_native_value = 0.0\n        elif \"[\" in self._attribute_name and \"]\" in self._attribute_name:\n            # Extracting the list index and attribute dynamically\n            attribute_name, index = self._attribute_name.split(\"[\")\n            index = int(index.split(\"]\")[0])\n            nested_attribute = (\n                self._attribute_name.split(\"].\")[1]\n                if \"].\" in self._attribute_name\n                else None\n            )\n\n            attribute = getattr(self.coordinator.data, attribute_name.split(\"[\")[0], [])\n\n            if index < len(attribute):\n                if nested_attribute is not None:\n                    new_native_value = getattr(attribute[index], nested_attribute, None)\n                else:\n                    new_native_value = attribute[index]\n            else:\n                new_native_value = None\n        elif \".\" in self._attribute_name:\n            attribute_parts = self._attribute_name.split(\".\")\n            attribute = self.coordinator.data\n            for part in attribute_parts:\n                attribute = getattr(attribute, part, None)\n            new_native_value = attribute\n\n        else:\n            new_native_value = getattr(\n                self.coordinator.data, self._attribute_name, None\n            )\n\n        if new_native_value is not None and self._conversion_factor is not None:\n            new_native_value *= self._conversion_factor\n\n        if (\n            new_native_value is not None\n            and new_native_value != 0.0\n            and self._version_translation_function is not None\n        ):\n            new_native_value = getattr(\n                hoymiles_wifi.hoymiles, self._version_translation_function\n            )(int(new_native_value))\n\n        if (\n            new_native_value is not None\n            and new_native_value != 0.0\n            and self._version_prefix is not None\n        ):\n            new_native_value = f\"{self._version_prefix}{new_native_value}\"\n\n        if (\n            self.entity_description.force_keep_maximum_within_day\n            and self._last_update_state is not None\n            and self._last_update_state.date() == datetime.now().date()\n        ):\n            new_native_value = max(new_native_value, self._native_value)\n\n        self._last_update_state = datetime.now()\n        self._native_value = new_native_value\n\n    async def async_added_to_hass(self) -> None:\n        \"\"\"Call when entity about to be added to hass.\"\"\"\n        await super().async_added_to_hass()\n\n        state = await self.async_get_last_sensor_data()\n        if state:\n            self.last_known_value = state.native_value\n\n\nclass HoymilesEnergySensorEntity(HoymilesDataSensorEntity, RestoreSensor):\n    \"\"\"Represents an energy sensor entity for Hoymiles data.\"\"\"\n\n    def __init__(\n        self,\n        config_entry: ConfigEntry,\n        description: HoymilesDiagnosticEntityDescription,\n        coordinator: HoymilesCoordinatorEntity,\n    ):\n        \"\"\"Initialize the HoymilesEnergySensorEntity.\"\"\"\n        super().__init__(config_entry, description, coordinator)\n        # Important to set to None to not mess with long term stats\n        self._last_known_value = None\n\n    def schedule_midnight_reset(self, reset_sensor_value: bool = True):\n        \"\"\"Schedule the reset function to run again at the next midnight.\"\"\"\n        now = datetime.now()\n        midnight = datetime.combine(now.date(), time(0, 0))\n        midnight = midnight + timedelta(days=1) if now > midnight else midnight\n        time_until_midnight = (midnight - datetime.now()).total_seconds()\n\n        if reset_sensor_value:\n            self.reset_sensor_value()\n\n        self.hass.loop.call_later(time_until_midnight, self.schedule_midnight_reset)\n\n    def reset_sensor_value(self):\n        \"\"\"Reset the sensor value.\"\"\"\n        self._last_known_value = 0\n\n    @property\n    def native_value(self):\n        \"\"\"Return the native value of the sensor.\"\"\"\n        super_native_value = super().native_value\n        # For an energy sensor a value of 0 would mess up long term stats because of how total_increasing works\n        if super_native_value == 0.0:\n            _LOGGER.debug(\n                \"Returning last known value instead of 0.0 for %s to avoid resetting total_increasing counter\",\n                self.name,\n            )\n            self._assumed_state = True\n            return self._last_known_value\n        self._last_known_value = super_native_value\n        self._assumed_state = False\n        return super_native_value\n\n    async def async_added_to_hass(self) -> None:\n        \"\"\"Call when entity about to be added to hass.\"\"\"\n        await super().async_added_to_hass()\n\n        state = await self.async_get_last_sensor_data()\n        if state:\n            self._last_known_value = state.native_value\n\n        if self.entity_description.reset_at_midnight:\n            self.schedule_midnight_reset(reset_sensor_value=False)\n\n\nclass HoymilesDiagnosticSensorEntity(\n    HoymilesCoordinatorEntity, RestoreSensor, SensorEntity\n):\n    \"\"\"Represents a diagnostic sensor entity for Hoymiles data.\"\"\"\n\n    def __init__(self, config_entry, description, coordinator):\n        \"\"\"Initialize the HoymilesSensorEntity.\"\"\"\n        super().__init__(config_entry, description, coordinator)\n\n        self._attribute_name = description.key\n        self._conversion = description.conversion\n        self._separator = description.separator\n        self._native_value = None\n        self._assumed_state = False\n\n        self.update_state_value()\n        self._last_known_value = self._native_value\n\n    @callback\n    def _handle_coordinator_update(self) -> None:\n        \"\"\"Handle updated data from the coordinator.\"\"\"\n        self.update_state_value()\n        super()._handle_coordinator_update()\n\n    @property\n    def native_value(self):\n        \"\"\"Return the native value of the sensor.\"\"\"\n        if self._native_value is None:\n            self._assumed_state = True\n            return self._last_known_value\n\n        self._last_known_value = self._native_value\n        self._assumed_state = False\n        return self._native_value\n\n    def update_state_value(self):\n        \"\"\"Update the state value of the sensor.\"\"\"\n\n        if \"[\" in self._attribute_name and \"]\" in self._attribute_name:\n            attribute_parts = self._attribute_name.split(\"[\")\n            attribute_name = attribute_parts[0]\n            index_range = attribute_parts[1].split(\"]\")[0]\n            start, end = map(int, index_range.split(\"-\"))\n\n            new_attribute_names = [\n                f\"{attribute_name}{i}\" for i in range(start, end + 1)\n            ]\n            attribute_values = [\n                str(getattr(self.coordinator.data, attr, \"\"))\n                for attr in new_attribute_names\n            ]\n\n            if \"\" in attribute_values:\n                self._native_value = None\n            else:\n                self._native_value = self._separator.join(attribute_values)\n        else:\n            self._native_value = getattr(\n                self.coordinator.data, self._attribute_name, None\n            )\n\n        if self._native_value is not None and self._conversion == ConversionAction.HEX:\n            self._native_value = self._separator.join(\n                hex(int(value))[2:]\n                for value in self._native_value.split(self._separator)\n            ).upper()\n\n    async def async_added_to_hass(self) -> None:\n        \"\"\"Call when entity about to be added to hass.\"\"\"\n        await super().async_added_to_hass()\n        state = await self.async_get_last_sensor_data()\n        if state:\n            self._last_known_value = state.native_value\n\n\nclass HoymilesEnergyStorageSensorEntity(HoymilesCoordinatorEntity, RestoreSensor):\n    \"\"\"Represents a sensor entity for Hoymiles data.\"\"\"\n\n    def __init__(\n        self,\n        config_entry: ConfigEntry,\n        description: HoymilesEnergyStorageSensorEntityDescription,\n        coordinator: HoymilesCoordinatorEntity,\n    ):\n        \"\"\"Pass coordinator to CoordinatorEntity.\"\"\"\n        super().__init__(config_entry, description, coordinator)\n\n        self._attribute_name = description.key\n        self._conversion_factor = description.conversion_factor\n        self._version_translation_function = description.version_translation_function\n        self._version_prefix = description.version_prefix\n        self._native_value = None\n        self._assumed_state = False\n        self._last_known_value = None\n        self._last_successful_update = None\n        self._last_update_state = None\n\n        self.update_state_value()\n\n    @callback\n    def _handle_coordinator_update(self) -> None:\n        \"\"\"Handle updated data from the coordinator.\"\"\"\n        self.update_state_value()\n        super()._handle_coordinator_update()\n\n    @property\n    def native_value(self):\n        \"\"\"Return the native value of the sensor.\"\"\"\n        if self._native_value == 0.0:\n            if self.entity_description.assume_state:\n                return self._last_known_value\n            elif (\n                self._last_successful_update is not None\n                and datetime.now() - self._last_successful_update\n                <= timedelta(minutes=3)\n            ):\n                _LOGGER.debug(\n                    \"[%s] Returning last known value: %s, instead of 0.0 to cope with inverter in offline mode.\",\n                    self.name,\n                    self._last_known_value,\n                )\n                self._assumed_state = True\n                return self._last_known_value\n        else:\n            self._last_successful_update = datetime.now()\n            self._last_known_value = self._native_value\n        self._assumed_state = False\n        return self._native_value\n\n    @property\n    def assumed_state(self):\n        \"\"\"Return the assumed state of the sensor.\"\"\"\n        return self._assumed_state\n\n    def update_state_value(self):\n        \"\"\"Update the state value of the sensor based on the coordinator data.\"\"\"\n        new_native_value = 0.0\n\n        if (\n            self.coordinator is None\n            or not hasattr(self.coordinator, \"data\")\n            or self.coordinator.data is None\n        ):\n            self._native_value = 0.0\n            return\n\n        def resolve_path(obj, path):\n            tokens = re.findall(r\"\\w+|\\[\\d+\\]\", path)\n            for token in tokens:\n                if obj is None:\n                    return None\n                if token.startswith(\"[\"):\n                    index = int(token[1:-1])\n                    try:\n                        obj = obj[index]\n                    except (IndexError, TypeError):\n                        logging.error(\n                            \"Index %d out of range for object: %s\", index, obj\n                        )\n                        return None\n                else:\n                    obj = getattr(obj, token, None)\n            return obj\n\n        new_native_value = resolve_path(self.coordinator.data, self._attribute_name)\n\n        if new_native_value is not None and self._conversion_factor is not None:\n            new_native_value *= self._conversion_factor\n\n        if (\n            new_native_value is not None\n            and new_native_value != 0.0\n            and self._version_translation_function is not None\n        ):\n            new_native_value = getattr(\n                hoymiles_wifi.hoymiles, self._version_translation_function\n            )(int(new_native_value))\n\n        if (\n            new_native_value is not None\n            and new_native_value != 0.0\n            and self._version_prefix is not None\n        ):\n            new_native_value = f\"{self._version_prefix}{new_native_value}\"\n\n        if (\n            self.entity_description.force_keep_maximum_within_day\n            and self._last_update_state is not None\n            and self._last_update_state.date() == datetime.now().date()\n        ):\n            new_native_value = max(new_native_value, self._native_value)\n\n        self._last_update_state = datetime.now()\n        self._native_value = new_native_value\n\n        async def async_added_to_hass(self) -> None:\n            \"\"\"Call when entity about to be added to hass.\"\"\"\n            await super().async_added_to_hass()\n\n            state = await self.async_get_last_sensor_data()\n            if state:\n                self.last_known_value = state.native_value\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/services.py",
    "content": "from homeassistant.core import ServiceCall\nfrom hoymiles_wifi.dtu import DTU\n\nfrom hoymiles_wifi.hoymiles import BMSWorkingMode\n\nfrom custom_components.hoymiles_wifi.const import HASS_DTU, DOMAIN\nfrom homeassistant.helpers.device_registry import async_get as async_get_device_registry\n\n\nfrom hoymiles_wifi.utils import parse_time_periods_input, parse_time_settings_input\n\nimport logging\n\nfrom .const import CONF_DTU_SERIAL_NUMBER\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_handle_set_bms_mode(call: ServiceCall):\n    hass = call.hass\n    device_registry = async_get_device_registry(hass)\n\n    bms_mode_str = call.data.get(\"bms_mode\")\n    rev_soc = call.data.get(\"rev_soc\")\n    max_power = call.data.get(\"max_power\")\n    peak_soc = call.data.get(\"peak_soc\", None)\n    peak_meter_power = call.data.get(\"peak_meter_power\", None)\n    time_settings_str = call.data.get(\"time_settings\", None)\n    time_periods_str = call.data.get(\"time_periods\", None)\n    device_ids = call.data.get(\"device_id\", [])\n\n    time_settings = None\n    time_periods = None\n\n    bms_working_mode = BMSWorkingMode[bms_mode_str.upper()]\n\n    _LOGGER.debug(f\"Setting BMS mode to {bms_working_mode}\")\n    _LOGGER.debug(f\"  rev_soc: {rev_soc}\")\n    _LOGGER.debug(f\"  max_power: {max_power}\")\n    _LOGGER.debug(f\"  peak_soc: {peak_soc}\")\n    _LOGGER.debug(f\"  peak_meter_power: {peak_meter_power}\")\n    _LOGGER.debug(f\"  time_settings_str: {time_settings_str}\")\n    _LOGGER.debug(f\"  time_periods_str: {time_periods_str}\")\n\n    if rev_soc is None:\n        raise ValueError(\"No reserve SOC provided!\")\n\n    if bms_working_mode == BMSWorkingMode.ECONOMIC:\n        time_settings = parse_time_settings_input(time_settings_str)\n        if not time_settings:\n            raise ValueError(\"Invalid time settings!\")\n\n    elif bms_working_mode in (\n        BMSWorkingMode.FORCED_CHARGING,\n        BMSWorkingMode.FORCED_DISCHARGE,\n    ):\n        if max_power is None:\n            raise ValueError(\"No max power provided!\")\n\n    elif bms_working_mode == BMSWorkingMode.PEAK_SHAVING:\n        if peak_soc is None:\n            raise ValueError(\"No peak SOC provided!\")\n        if peak_meter_power is None:\n            raise ValueError(\"No peak meter power provided!\")\n\n    elif bms_working_mode == BMSWorkingMode.TIME_OF_USE:\n        time_periods = parse_time_periods_input(time_periods_str)\n        if not time_periods:\n            raise ValueError(\"Invalid time periods!\")\n\n    for device_id in device_ids:\n        device = device_registry.async_get(device_id)\n        if not device:\n            _LOGGER.error(f\"Device {device_id} not found in registry\")\n            continue\n\n        for entry_id in device.config_entries:\n            hass_data = hass.data[DOMAIN].get(entry_id)\n            if not hass_data:\n                continue\n\n            dtu = hass_data[HASS_DTU]\n            if not dtu or not isinstance(dtu, DTU):\n                _LOGGER.error(f\"DTU not found for entry {entry_id}\")\n                continue\n\n            _LOGGER.debug(\"Found DTU for entry %s -> %s\", entry_id, dtu)\n\n            dtu_serial_number_str = hass_data.get(CONF_DTU_SERIAL_NUMBER, None)\n\n            if not dtu_serial_number_str:\n                _LOGGER.error(f\"DTU serial number not found in config entry {entry_id}\")\n                continue\n\n            dtu_serial_number = int(dtu_serial_number_str)\n            inverter_serial_number = int(device.serial_number)\n\n            _LOGGER.debug(\n                f\"Setting BMS mode for inverter_serial_number: {inverter_serial_number}\"\n            )\n\n            await dtu.async_set_energy_storage_working_mode(\n                dtu_serial_number=dtu_serial_number,\n                inverter_serial_number=inverter_serial_number,\n                bms_working_mode=bms_working_mode,\n                rev_soc=rev_soc,\n                time_settings=time_settings,\n                max_power=max_power,\n                peak_soc=peak_soc,\n                peak_meter_power=peak_meter_power,\n                time_periods=time_periods,\n            )\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/services.yaml",
    "content": "set_bms_mode:\n  name: Set BMS Working Mode\n  description: Sets the working mode of the Hoymiles hybrid inverter.\n  target:\n    device:\n      integration: hoymiles_wifi\n  fields:\n    bms_mode:\n      required: true\n      example: self_use\n      selector:\n        select:\n          options:\n            - \"self_use\"\n            - \"economic\"\n            - \"backup_power\"\n            - \"pure_off_grid\"\n            - \"forced_charging\"\n            - \"forced_discharge\"\n            - \"peak_shaving\"\n            - \"time_of_use\"\n          translation_key: \"bms_mode_type\"\n    rev_soc:\n      required: true\n      example: 50\n      selector:\n        number:\n          min: 0\n          max: 100\n          unit_of_measurement: \"%\"\n    max_power:\n      required: false\n      example: 60\n      selector:\n        number:\n          min: 0\n          max: 100\n          unit_of_measurement: \"%\"\n    peak_soc:\n      required: false\n      example: 80\n      selector:\n        number:\n          min: 0\n          max: 100\n          unit_of_measurement: \"%\"\n    peak_meter_power:\n      required: false\n      example: 100\n      selector:\n        number:\n          min: 0\n          unit_of_measurement: \"W\"\n    time_settings:\n      required: false\n      example: \"01.01-31.03:1,2,3=06:00-10:00-0.20-0.10,00:00-06:00-0.10-0.05,10:00-18:00-0.15-0.08;4,5=07:00-11:00-0.22-0.11,00:00-07:00-0.08-0.04,11:00-17:00-0.14-0.07\"\n      selector:\n        text:\n    time_periods:\n      required: false\n      example: \"06:00-08:00-50-90|18:00-20:00-40-20\"\n      selector:\n        text:\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/strings.json",
    "content": "{\n  \"config\": {\n    \"step\": {\n      \"user\": {\n        \"title\": \"Hoymiles DTU connection\",\n        \"description\": \"If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi\",\n        \"data\": {\n          \"host\": \"[%key:common::config_flow::data::host%]\",\n          \"update_interval\": \"Update Interval (seconds)\",\n          \"timeout\": \"Timeout (seconds)\"\n        }\n      },\n      \"reconfigure\": {\n        \"title\": \"Hoymiles DTU connection\",\n        \"description\": \"If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi\",\n        \"data\": {\n          \"host\": \"[%key:common::config_flow::data::host%]\",\n          \"update_interval\": \"Update Interval (seconds)\",\n          \"timeout\": \"Timeout (seconds)\"\n        }\n      }\n    },\n    \"error\": {\n      \"cannot_connect\": \"[%key:common::config_flow::error::cannot_connect%]\"\n    },\n    \"abort\": {\n      \"already_configured\": \"[%key:common::config_flow::abort::already_configured_device%]\"\n    }\n  },\n  \"entity\": {\n    \"binary_sensor\": {\n      \"dtu\": {\n        \"name\": \"DTU\"\n      }\n    },\n    \"number\": {\n      \"limit_power_mypower\": {\n        \"name\": \"Power limit\"\n      }\n    },\n    \"sensor\": {\n      \"ac_active_power\": {\n        \"name\": \"AC power\"\n      },\n      \"ac_daily_energy\": {\n        \"name\": \"AC daily energy\"\n      },\n      \"ac_reactive_power\": {\n        \"name\": \"AC reactive power\"\n      },\n      \"grid_voltage\": {\n        \"name\": \"Grid voltage\"\n      },\n      \"ac_current\": {\n        \"name\": \"AC current\" \n      },\n      \"grid_frequency\": {\n        \"name\": \"Grid frequency\"\n      },\n      \"inverter_power_factor\": {\n        \"name\": \"Power factor\"\n      },\n      \"inverter_temperature\": {\n        \"name\": \"Temperature\"\n      },\n      \"inverter_warning_number\": {\n        \"name\": \"Warning number\"\n      },\n      \"port_dc_voltage\": {\n        \"name\": \"Port {port_number} DC voltage\"\n      },\n      \"port_dc_current\": {\n        \"name\": \"Port {port_number} DC current\"\n      },\n      \"port_dc_power\": {\n        \"name\": \"Port {port_number} DC power\"\n      },\n      \"port_dc_total_energy\": {\n        \"name\": \"Port {port_number} DC total energy\"\n      },\n      \"port_dc_daily_energy\": {\n        \"name\": \"Port {port_number} DC daily energy\"\n      },\n      \"port_error_code\": {\n        \"name\": \"Port {port_number} error code\"\n      },\n      \"wifi_ssid\": {\n        \"name\": \"Wi-Fi SSID\"\n      },\n      \"meter_kind\": {\n        \"name\": \"Meter kind\"\n      },\n      \"mac_address\": {\n        \"name\": \"MAC address\"\n      },\n      \"ip_address\": {\n        \"name\": \"IP address\"\n      },\n      \"dtu_ap_ssid\": {\n        \"name\": \"AP SSID\"\n      },\n      \"dtu_sw_version\": {\n        \"name\": \"SW version\"\n      },\n      \"dtu_hw_version\": {\n        \"name\": \"HW version\"\n      },\n      \"pv_sw_version\": {\n        \"name\": \"SW version\"\n      },\n      \"pv_hw_version\": {\n        \"name\": \"HW version\"\n      },\n      \"signal_strength\": {\n        \"name\": \"Signal strength\"\n      },\n      \"voltage_phase_A\": {\n        \"name\": \"Voltage phase A\"\n      },\n      \"voltage_phase_B\": {\n        \"name\": \"Voltage phase B\"\n      },\n      \"voltage_phase_C\": {\n        \"name\": \"Voltage phase C\"\n      },\n      \"voltage_line_AB\": {\n        \"name\": \"Voltage line AB\"\n      },\n      \"voltage_line_BC\": {\n        \"name\": \"Voltage line BC\"\n      },\n      \"voltage_line_CA\": {\n        \"name\": \"Voltage line CA\"\n      },\n      \"phase_total_power\": {\n        \"name\": \"Phase total power\"\n      },\n      \"phase_A_power\": {\n        \"name\": \"Phase A power\"\n      },\n      \"phase_B_power\": {\n        \"name\": \"Phase B power\"\n      },\n      \"phase_C_power\": {\n        \"name\": \"Phase C power\"\n      },\n      \"power_factor_total\": {\n        \"name\": \"Power factor total\"\n      },\n      \"energy_total_power\": {\n        \"name\": \"Energy total power\"\n      },\n      \"energy_phase_A\": {\n        \"name\": \"Energy phase A\"\n      },\n      \"energy_phase_B\": {\n        \"name\": \"Energy phase B\"\n      },\n      \"energy_phase_C\": {\n        \"name\": \"Energy phase C\"\n      },\n      \"energy_total_consumed\": {\n        \"name\": \"Energy total consumed\"\n      },\n      \"energy_phase_A_consumed\": {\n        \"name\": \"Energy phase A consumed\"\n      },\n      \"energy_phase_B_consumed\": {\n        \"name\": \"Energy phase B consumed\"\n      },\n      \"energy_phase_C_consumed\": {\n        \"name\": \"Energy phase C consumed\"\n      },\n      \"current_phase_A\": {\n        \"name\": \"Current phase A\"\n      },\n      \"current_phase_B\": {\n        \"name\": \"Current phase B\"\n      },\n      \"current_phase_C\": {\n        \"name\": \"Current phase C\"\n      },\n      \"power_factor_phase_A\": {\n        \"name\": \"Power factor phase A\"\n      },\n      \"power_factor_phase_B\": {\n        \"name\": \"Power factor phase B\"\n      },\n      \"power_factor_phase_C\": {\n        \"name\": \"Power factor phase C\"\n      },\n      \"energy_to_load\": {\n        \"name\": \"Energy to load\"\n      },\n      \"energy_to_battery\": {\n        \"name\": \"Energy to battery\"\n      },\n      \"energy_to_grid\": {\n        \"name\": \"Energy to grid\"\n      },\n      \"energy_from_pv\": {\n        \"name\": \"Energy from PV\"\n      },\n      \"energy_from_battery\": {\n        \"name\": \"Energy from battery\"\n      },\n      \"energy_from_grid\": {\n        \"name\": \"Energy from grid\"\n      },\n      \"pv_panel_voltage\": {\n        \"name\": \"PV panel {port_number} voltage\"\n      },\n      \"pv_panel_current\": {\n        \"name\": \"PV panel {port_number} current\"\n      },\n      \"pv_panel_power\": {\n        \"name\": \"PV panel {port_number} power\"\n      },\n      \"pv_panel_energy\": {\n        \"name\": \"PV panel {port_number} energy\"\n      },\n      \"state_of_charge\": {\n        \"name\": \"State of charge\"\n      },\n      \"state_of_health\": {\n        \"name\": \"State of health\"\n      },\n      \"battery_voltage\": {\n        \"name\": \"Battery voltage\"\n      },\n      \"internal_charge_mode\": {\n        \"name\": \"Internal charge mode\"\n      },\n      \"internal_discharge_mode\": {\n        \"name\": \"Internal discharge mode\"\n      },\n      \"cell_voltage_high\": {\n        \"name\": \"Cell voltage high\"\n      },\n      \"cell_voltage_low\": {\n        \"name\": \"Cell voltage low\"\n      },\n      \"temp_high_charge\": {\n        \"name\": \"Temperature high (charge)\"\n      },\n      \"temp_low_charge\": {\n        \"name\": \"Temperature low (charge)\"\n      },\n      \"temp_high_module\": {\n        \"name\": \"Module temperature high\"\n      },\n      \"temp_low_module\": {\n        \"name\": \"Module temperature low\"\n      },\n      \"energy_charged\": {\n        \"name\": \"Energy charged\"\n      },\n      \"energy_discharged\": {\n        \"name\": \"Energy discharged\"\n      },\n      \"voltage_charge_high\": {\n        \"name\": \"Voltage charge high\"\n      },\n      \"voltage_charge_low\": {\n        \"name\": \"Voltage charge low\"\n      },\n      \"voltage_module_high\": {\n        \"name\": \"Module voltage high\"\n      },\n      \"voltage_module_low\": {\n        \"name\": \"Module voltage low\"\n      },\n      \"grid_status\": {\n        \"name\": \"Grid status\"\n      },\n      \"grid_power_factor_deviation\": {\n        \"name\": \"Power factor deviation\"\n      },\n      \"grid_voltage_phase\": {\n        \"name\": \"Grid voltage phase {phase}\"\n      },\n      \"grid_current_phase\": {\n        \"name\": \"Grid current phase {phase}\"\n      },\n      \"grid_reactive_power_phase\": {\n        \"name\": \"Grid reactive power phase {phase}\"\n      },\n      \"grid_active_power_phase\": {\n        \"name\": \"Grid active power phase {phase}\"\n      },\n      \"grid_power_factor_phase\": {\n        \"name\": \"Grid power factor phase {phase}\"\n      },\n      \"grid_energy_frequency_phase\": {\n        \"name\": \"Grid energy frequency phase {phase}\"\n      },\n      \"grid_energy_consumed_phase\": {\n        \"name\": \"Grid energy consumed phase {phase}\"\n      },\n      \"load_status\": {\n        \"name\": \"Load status\"\n      },\n      \"load_frequency\": {\n        \"name\": \"Load frequency\"\n      },\n      \"load_voltage_phase\": {\n        \"name\": \"Load voltage phase {phase}\"\n      },\n      \"load_active_power_phase\": {\n        \"name\": \"Load active power phase {phase}\"\n      },\n      \"load_energy_consumed_phase\": {\n        \"name\": \"Load energy consumed phase {phase}\"\n      },\n      \"inverter_status\": {\n        \"name\": \"Inverter status\"\n      },\n      \"inverter_frequency\": {\n        \"name\": \"Inverter frequency\"\n      },\n      \"inverter_isolation_resistance\": {\n        \"name\": \"Inverter isolation resistance\"\n      },\n      \"inverter_leakage_current\": {\n        \"name\": \"Inverter leakage current\"\n      },\n      \"inverter_drm_signal\": {\n        \"name\": \"Inverter DRM signal\"\n      },\n      \"inverter_voltage_phase\": {\n        \"name\": \"Inverter voltage phase {phase}\"\n      },\n      \"inverter_current_phase\": {\n        \"name\": \"Inverter current phase {phase}\"\n      },\n      \"inverter_active_power_phase\": {\n        \"name\": \"Inverter active power phase {phase}\"\n      },\n      \"inverter_reactive_power_phase\": {\n        \"name\": \"Inverter reactive power phase {phase}\"\n      },\n      \"inverter_dc_current_phase\": {\n        \"name\": \"Inverter DC current phase {phase}\"\n      },\n      \"inverter_dc_voltage_phase\": {\n        \"name\": \"Inverter DC voltage phase {phase}\"\n      },\n      \"inverter_eps_voltage_phase\": {\n        \"name\": \"Inverter EPS voltage phase {phase}\"\n      },\n      \"inverter_eps_current_phase\": {\n        \"name\": \"Inverter EPS current phase {phase}\"\n      },\n      \"inverter_eps_power_phase\": {\n        \"name\": \"Inverter EPS power phase {phase}\"\n      },\n      \"pv_inverter_status\": {\n        \"name\": \"PV-Inverter status\"\n      },\n      \"pv_inverter_frequency\": {\n        \"name\": \"PV-Inverter frequency\"\n      },\n      \"pv_inverter_voltage_phase\": {\n        \"name\": \"PV-Inverter voltage phase {phase}\"\n      },\n      \"pv_inverter_current_phase\": {\n        \"name\": \"PV-Inverter current phase {phase}\"\n      },\n      \"pv_inverter_active_power_phase\": {\n        \"name\": \"PV-Inverter active power phase {phase}\"\n      },\n      \"pv_inverter_reactive_power_phase\": {\n        \"name\": \"PV-Inverter reactive power phase {phase}\"\n      },\n      \"pv_inverter_energy_phase\": {\n        \"name\": \"PV-Inverter energy phase {phase}\"\n      },\n      \"pv_to_load\": {\n        \"name\": \"Power PV to load\"\n      },\n      \"battery_to_load\": {\n        \"name\": \"Power battery to load\"\n      },\n      \"grid_to_load\": {\n        \"name\": \"Power grid to load\"\n      },\n      \"pv_to_battery\": {\n        \"name\": \"Power PV to battery\"\n      },\n      \"pv_to_grid\": {\n        \"name\": \"Power PV to grid\"\n      },\n      \"battery_to_grid\": {\n        \"name\": \"Power battery to grid\"\n      }\n    },\n    \"button\": {\n      \"restart\": {\n        \"name\": \"Restart\"\n      },\n      \"turn_off\": {\n        \"name\": \"Turn off\"\n      },\n      \"turn_on\": {\n        \"name\": \"Turn on\"\n      },\n      \"enable_performance_data_mode\": {\n        \"name\": \"Experimental: Enable performance data mode\"\n      }\n    }\n  },\n  \"device\": {\n    \"inverter\": {\n      \"name\": \"Inverter\"\n    },\n    \"dtu\": {\n      \"name\": \"DTU\"\n    },\n    \"meter\": {\n      \"name\": \"Meter\"\n    },\n    \"hybrid_inverter\": {\n      \"name\": \"Hybrid inverter\"\n    }\n  },\n  \"services\": {\n    \"set_bms_mode\": {\n      \"name\": \"Set BMS mode\",\n      \"description\": \"Set the BMS mode of the connected battery.\",\n      \"fields\": {\n        \"bms_mode\": {\n          \"name\": \"BMS mode\",\n          \"description\": \"The BMS working mode to set. Possible values are: 'self_use', 'economic', 'backup_power', 'pure_off_grid', 'forced_charging', 'forced_discharge', 'peak_shaving', 'time_of_use'\"\n        },\n        \"rev_soc\": {\n          \"name\": \"Reserved SOC\",\n          \"description\": \"The reserved state of charge (SOC) to set (in %).\"\n        },\n        \"max_power\": {\n          \"name\": \"Max Power\",\n          \"description\": \"The maximum charge/discharge power to set (in %).\"\n        },\n        \"peak_soc\": {\n          \"name\": \"Peak SOC\",\n          \"description\": \"The peak state of charge (SOC) to set (in %).\"\n        },\n        \"peak_meter_power\": {\n          \"name\": \"Peak Meter Power\",\n          \"description\": \"The peak meter power to set (in W).\"\n        },\n        \"time_settings\": {\n          \"name\": \"Time Settings\",\n          \"description\": \"Configure time-of-use settings.\"\n        },\n        \"time_periods\": {\n          \"name\": \"Time Periods\",\n          \"description\": \"Define time periods for time-of-use mode.\"\n        }\n      }\n    }\n  },\n  \"selector\": {\n    \"bms_mode_type\": {\n      \"options\": {\n        \"self_use\": \"Self-Consumption Mode translated\",\n        \"economic\": \"Economy Mode\",\n        \"backup_power\": \"Backup Mode\",\n        \"pure_off_grid\": \"Off-Grid Mode\",\n        \"forced_charging\": \"Force Charge Mode\",\n        \"forced_discharge\": \"Force Discharge Mode\",\n        \"peak_shaving\": \"Peak Shaving Mode\",\n        \"time_of_use\": \"Time of Use Mode\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/translations/de.json",
    "content": "{\n  \"config\": {\n    \"step\": {\n      \"user\": {\n        \"title\": \"Hoymiles Verbindung\",\n        \"description\": \"Wenn Sie Hilfe bei der Konfiguration benötigen, schauen Sie hier vorbei: https://github.com/suaveolent/ha-hoymiles-wifi\",\n        \"data\": {\n          \"host\": \"Host\",\n          \"update_interval\": \"Aktualisierungsintervall (Sekunden)\",\n          \"timeout\": \"Timeout (Sekunden)\"\n        }\n      },\n      \"reconfigure\": {\n        \"title\": \"Hoymiles Verbindung\",\n        \"description\": \"Wenn Sie Hilfe bei der Konfiguration benötigen, schauen Sie hier vorbei: https://github.com/suaveolent/ha-hoymiles-wifi\",\n        \"data\": {\n          \"host\": \"Host\",\n          \"update_interval\": \"Aktualisierungsintervall (Sekunden)\",\n          \"timeout\": \"Timeout (Sekunden)\"\n        }\n      }\n    },\n    \"error\": {\n      \"cannot_connect\": \"Verbindung nicht möglich.\"\n    },\n    \"abort\": {\n      \"already_configured\": \"Bereits konfiguriert.\"\n    }\n  },\n  \"entity\": {\n    \"binary_sensor\": {\n      \"dtu\": {\n        \"name\": \"DTU\"\n      }\n    },\n    \"number\": {\n      \"limit_power_mypower\": {\n        \"name\": \"Leistungsbegrenzung\"\n      }\n    },\n    \"sensor\": {\n      \"ac_active_power\": {\n        \"name\": \"AC-Leistung\"\n      },\n      \"ac_daily_energy\": {\n        \"name\": \"AC-Tagesenergie\"\n      },\n      \"ac_reactive_power\": {\n        \"name\": \"AC-Blindleistung\"\n      },\n      \"grid_voltage\": {\n        \"name\": \"Netzspannung\"\n      },\n      \"ac_current\": {\n        \"name\": \"AC-Strom\" \n      },\n      \"grid_frequency\": {\n        \"name\": \"Netzfrequenz\"\n      },\n      \"inverter_power_factor\": {\n        \"name\": \"Leistungsfaktor\"\n      },\n      \"inverter_temperature\": {\n        \"name\": \"Temperatur\"\n      },\n      \"inverter_warning_number\": {\n        \"name\": \"Warnnummer\"\n      },\n      \"port_dc_voltage\": {\n        \"name\": \"Port {port_number} DC-Spannung\"\n      },\n      \"port_dc_current\": {\n        \"name\": \"Port {port_number} DC-Strom\"\n      },\n      \"port_dc_power\": {\n        \"name\": \"Port {port_number} DC-Leistung\"\n      },\n      \"port_dc_total_energy\": {\n        \"name\": \"Port {port_number} DC-Gesamtenergie\"\n      },\n      \"port_dc_daily_energy\": {\n        \"name\": \"Port {port_number} DC-Tagesenergie\"\n      },\n      \"port_error_code\": {\n        \"name\": \"Port {port_number} Fehlercode\"\n      },\n      \"wifi_ssid\": {\n        \"name\": \"WLAN-SSID\"\n      },\n      \"meter_kind\": {\n        \"name\": \"Zählermodell\"\n      },\n      \"mac_address\": {\n        \"name\": \"MAC-Adresse\"\n      },\n      \"ip_address\": {\n        \"name\": \"IP Adresse\"\n      },\n      \"dtu_ap_ssid\": {\n        \"name\": \"AP-SSID\"\n      },\n      \"dtu_sw_version\": {\n        \"name\": \"SW-Version\"\n      },\n      \"dtu_hw_version\": {\n        \"name\": \"HW-Version\"\n      },\n      \"pv_sw_version\": {\n        \"name\": \"SW-Version\"\n      },\n      \"pv_hw_version\": {\n        \"name\": \"HW-Version\"\n      },\n      \"signal_strength\": {\n        \"name\": \"Signalstärke\"\n      },\n      \"voltage_phase_A\": {\n        \"name\": \"Spannung Phase A\"\n      },\n      \"voltage_phase_B\": {\n        \"name\": \"Spannung Phase B\"\n      },\n      \"voltage_phase_C\": {\n        \"name\": \"Spannung Phase C\"\n      },\n      \"voltage_line_AB\": {\n        \"name\": \"Spannung Phase AB\"\n      },\n      \"voltage_line_BC\": {\n        \"name\": \"Spannung Phase BC\"\n      },\n      \"voltage_line_CA\": {\n        \"name\": \"Spannung Phase CA\"\n      },\n      \"phase_total_power\": {\n        \"name\": \"Phasengesamtleistung\"\n      },\n      \"phase_A_power\": {\n        \"name\": \"Leistung Phase A\"\n      },\n      \"phase_B_power\": {\n        \"name\": \"Leistung Phase B\"\n      },\n      \"phase_C_power\": {\n        \"name\": \"Leistung Phase C\"\n      },\n      \"power_factor_total\": {\n        \"name\": \"Gesamtleistungsfaktor\"\n      },\n      \"energy_total_power\": {\n        \"name\": \"Gesamtenergie\"\n      },\n      \"energy_phase_A\": {\n        \"name\": \"Energie Phase A\"\n      },\n      \"energy_phase_B\": {\n        \"name\": \"Energie Phase B\"\n      },\n      \"energy_phase_C\": {\n        \"name\": \"Energie Phase C\"\n      },\n      \"energy_total_consumed\": {\n        \"name\": \"Gesamtverbrauchte Energie\"\n      },\n      \"energy_phase_A_consumed\": {\n        \"name\": \"Verbrauchte Energie Phase A\"\n      },\n      \"energy_phase_B_consumed\": {\n        \"name\": \"Verbrauchte Energie Phase B\"\n      },\n      \"energy_phase_C_consumed\": {\n        \"name\": \"Verbrauchte Energie Phase C\"\n      },\n      \"current_phase_A\": {\n        \"name\": \"Strom Phase A\"\n      },\n      \"current_phase_B\": {\n        \"name\": \"Strom Phase B\"\n      },\n      \"current_phase_C\": {\n        \"name\": \"Strom Phase C\"\n      },\n      \"power_factor_phase_A\": {\n        \"name\": \"Leistungsfaktor Phase A\"\n      },\n      \"power_factor_phase_B\": {\n        \"name\": \"Leistungsfaktor Phase B\"\n      },\n      \"power_factor_phase_C\": {\n        \"name\": \"Leistungsfaktor Phase C\"\n      },\n      \"energy_to_load\": {\n        \"name\": \"Energie zu Last\"\n      },\n      \"energy_to_battery\": {\n        \"name\": \"Energie zu Batterie\"\n      },\n      \"energy_to_grid\": {\n        \"name\": \"Energie zu Netz\"\n      },\n      \"energy_from_pv\": {\n        \"name\": \"Energie aus PV\"\n      },\n      \"energy_from_battery\": {\n        \"name\": \"Energie aus Batterie\"\n      },\n      \"energy_from_grid\": {\n        \"name\": \"Energie aus Netz\"\n      },\n      \"pv_panel_voltage\": {\n        \"name\": \"PV panel {port_number} voltage\"\n      },\n      \"pv_panel_current\": {\n        \"name\": \"PV panel {port_number} current\"\n      },\n      \"pv_panel_power\": {\n        \"name\": \"PV panel {port_number} power\"\n      },\n      \"pv_panel_energy\": {\n        \"name\": \"PV panel {port_number} energy\"\n      },\n      \"state_of_charge\": {\n        \"name\": \"Ladezustand\"\n      },\n      \"state_of_health\": {\n        \"name\": \"Gesundheitszustand\"\n      },\n      \"battery_voltage\": {\n        \"name\": \"Batteriespannung\"\n      },\n      \"internal_charge_mode\": {\n        \"name\": \"Interner Lademodus\"\n      },\n      \"internal_discharge_mode\": {\n        \"name\": \"Interner Entlademodus\"\n      },\n      \"cell_voltage_high\": {\n        \"name\": \"Maximale Zellenspannung \"\n      },\n      \"cell_voltage_low\": {\n        \"name\": \"Minimale Zellenspannung\"\n      },\n      \"temp_high_charge\": {\n        \"name\": \"Maximale Ladetemperatur\"\n      },\n      \"temp_low_charge\": {\n        \"name\": \"Minimale Ladetemperatur\"\n      },\n      \"temp_high_module\": {\n        \"name\": \"Maximale Modultemperatur\"\n      },\n      \"temp_low_module\": {\n        \"name\": \"Minimale Modultemperatur\"\n      },\n      \"energy_charged\": {\n        \"name\": \"Geladene Energie\"\n      },\n      \"energy_discharged\": {\n        \"name\": \"Entladene Energie\"\n      },\n      \"voltage_charge_high\": {\n        \"name\": \"Maximale Ladespannung\"\n      },\n      \"voltage_charge_low\": {\n        \"name\": \"Minimale Ladespannung\"\n      },\n      \"voltage_module_high\": {\n        \"name\": \"Maximale Modulspannung\"\n      },\n      \"voltage_module_low\": {\n        \"name\": \"Minimale Modulspannung\"\n      },\n      \"grid_status\": {\n        \"name\": \"Netzstatus\"\n      },\n      \"grid_power_factor_deviation\": {\n        \"name\": \"Leistungsfaktorabweichung\"\n      },\n      \"grid_voltage_phase\": {\n        \"name\": \"Netzspannung Phase {phase}\"\n      },\n      \"grid_current_phase\": {\n        \"name\": \"Netzstrom Phase {phase}\"\n      },\n      \"grid_reactive_power_phase\": {\n        \"name\": \"Blindleistung Phase {phase}\"\n      },\n      \"grid_active_power_phase\": {\n        \"name\": \"Wirkleistung Phase {phase}\"\n      },\n      \"grid_power_factor_phase\": {\n        \"name\": \"Leistungsfaktor Phase {phase}\"\n      },\n      \"grid_energy_frequency_phase\": {\n        \"name\": \"Energie-Frequenz Phase {phase}\"\n      },\n      \"grid_energy_consumed_phase\": {\n        \"name\": \"Verbrauchte Energie Phase {phase}\"\n      },\n      \"load_status\": {\n        \"name\": \"Laststatus\"\n      },\n      \"load_frequency\": {\n        \"name\": \"Lastfrequenz\"\n      },\n      \"load_voltage_phase\": {\n        \"name\": \"Lastspannung Phase {phase}\"\n      },\n      \"load_active_power_phase\": {\n        \"name\": \"Wirkleistung Phase {phase}\"\n      },\n      \"load_energy_consumed_phase\": {\n        \"name\": \"Verbrauchte Energie Phase {phase}\"\n      },\n      \"inverter_status\": {\n        \"name\": \"Wechselrichterstatus\"\n      },\n      \"inverter_frequency\": {\n        \"name\": \"Wechselrichterfrequenz\"\n      },\n      \"inverter_isolation_resistance\": {\n        \"name\": \"Isolationswiderstand des Wechselrichters\"\n      },\n      \"inverter_leakage_current\": {\n        \"name\": \"Ableitstrom des Wechselrichters\"\n      },\n      \"inverter_drm_signal\": {\n        \"name\": \"Wechselrichter-DRM-Signal\"\n      },\n      \"inverter_voltage_phase\": {\n        \"name\": \"Wechselrichterspannung Phase {phase}\"\n      },\n      \"inverter_current_phase\": {\n        \"name\": \"Wechselrichterstrom Phase {phase}\"\n      },\n      \"inverter_active_power_phase\": {\n        \"name\": \"Wechselrichterwirkleistung Phase {phase}\"\n      },\n      \"inverter_reactive_power_phase\": {\n        \"name\": \"Wechselrichterblindleistung Phase {phase}\"\n      },\n      \"inverter_dc_current_phase\": {\n        \"name\": \"DC-Strom Wechselrichter Phase {phase}\"\n      },\n      \"inverter_dc_voltage_phase\": {\n        \"name\": \"DC-Spannung Wechselrichter Phase {phase}\"\n      },\n      \"inverter_eps_voltage_phase\": {\n        \"name\": \"EPS-Spannung Wechselrichter Phase {phase}\"\n      },\n      \"inverter_eps_current_phase\": {\n        \"name\": \"EPS-Strom Wechselrichter Phase {phase}\"\n      },\n      \"inverter_eps_power_phase\": {\n        \"name\": \"EPS-Leistung Wechselrichter Phase {phase}\"\n      },\n      \"pv_inverter_status\": {\n        \"name\": \"PV-Wechselrichterstatus\"\n      },\n      \"pv_inverter_frequency\": {\n        \"name\": \"PV-Wechselrichterfrequenz\"\n      },\n      \"pv_inverter_voltage_phase\": {\n        \"name\": \"PV-Wechselrichter Spannung Phase {phase}\"\n      },\n      \"pv_inverter_current_phase\": {\n        \"name\": \"PV-Wechselrichterstrom Phase {phase}\"\n      },\n      \"pv_inverter_active_power_phase\": {\n        \"name\": \"PV-Wechselrichter Wirkleistung Phase {phase}\"\n      },\n      \"pv_inverter_reactive_power_phase\": {\n        \"name\": \"PV-Wechselrichter Blindleistung Phase {phase}\"\n      },\n      \"pv_inverter_energy_phase\": {\n        \"name\": \"PV-Wechselrichter Energie Phase {phase}\"\n      },\n      \"pv_to_load\": {\n        \"name\": \"Leistung PV zu Last\"\n      },\n      \"battery_to_load\": {\n        \"name\": \"Leistung Batterie zu Last\"\n      },\n      \"grid_to_load\": {\n        \"name\": \"Leistung Netz zu Last\"\n      },\n      \"pv_to_battery\": {\n        \"name\": \"Leistung PV zu Batterie\"\n      },\n      \"pv_to_grid\": {\n        \"name\": \"Leistung PV zu Netz\"\n      },\n      \"battery_to_grid\": {\n        \"name\": \"Leistung Batterie zu Netz\"\n      }\n    },\n    \"button\": {\n      \"restart\": {\n        \"name\": \"Neustart\"\n      },\n      \"turn_off\": {\n        \"name\": \"Ausschalten\"\n      },\n      \"turn_on\": {\n        \"name\": \"Einschalten\"\n      },\n      \"enable_performance_data_mode\": {\n        \"name\": \"Experimentell: Leistungsdatenmodus aktivieren\"\n      }\n    }\n  },\n  \"device\": {\n    \"inverter\": {\n      \"name\": \"Wechselrichter\"\n    },\n    \"dtu\": {\n      \"name\": \"DTU\"\n    },\n    \"meter\": {\n      \"name\": \"Zähler\"\n    },\n    \"hybrid_inverter\": {\n      \"name\": \"Hybrid-Wechselrichter\"\n    }\n  },\n  \"services\": {\n    \"set_bms_mode\": {\n      \"name\": \"BMS-Modus einstellen\",\n      \"description\": \"Den BMS-Modus der angeschlossenen Batterie einstellen.\",\n      \"fields\": {\n        \"bms_mode\": {\n          \"name\": \"BMS-Modus\",\n          \"description\": \"Der einzustellende BMS-Arbeitsmodus. Mögliche Werte sind: 'self_use', 'economic', 'backup_power', 'pure_off_grid', 'forced_charging', 'forced_discharge', 'peak_shaving', 'time_of_use'\"\n        },\n        \"rev_soc\": {\n          \"name\": \"Reservierter SOC\",\n          \"description\": \"Der einzustellende reservierte Ladezustand (SOC) in %.\"\n        },\n        \"max_power\": {\n          \"name\": \"Maximale Leistung\",\n          \"description\": \"Die einzustellende maximale Lade-/Entladeleistung in %.\"\n        },\n        \"peak_soc\": {\n          \"name\": \"Spitzen-SOC\",\n          \"description\": \"Der einzustellende Spitzen-Ladezustand (SOC) in %.\"\n        },\n        \"peak_meter_power\": {\n          \"name\": \"Spitzenzählerleistung\",\n          \"description\": \"Die einzustellende Spitzenzählerleistung in W.\"\n        },\n        \"time_settings\": {\n          \"name\": \"Zeiteinstellungen\",\n          \"description\": \"Zeiteinstellungen für den Nutzungszeitmodus konfigurieren.\"\n        },\n        \"time_periods\": {\n          \"name\": \"Zeitabschnitte\",\n          \"description\": \"Zeitabschnitte für den Nutzungszeitmodus festlegen.\"\n        }\n      }\n    }\n  },\n  \"selector\": {\n    \"bms_mode_type\": {\n      \"options\": {\n        \"self_use\": \"Eigenverbrauchsmodus\",\n        \"economic\": \"Sparmodus\",\n        \"backup_power\": \"Notstrommodus\",\n        \"pure_off_grid\": \"Inselbetriebsmodus\",\n        \"forced_charging\": \"Erzwungener Ladebetrieb\",\n        \"forced_discharge\": \"Erzwungener Entladebetrieb\",\n        \"peak_shaving\": \"Lastspitzenkappungsmodus\",\n        \"time_of_use\": \"Nutzungszeitmodus\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/translations/en.json",
    "content": "{\n  \"config\": {\n    \"step\": {\n      \"user\": {\n        \"title\": \"Hoymiles DTU connection\",\n        \"description\": \"If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi\",\n        \"data\": {\n          \"host\": \"Host\",\n          \"update_interval\": \"Update Interval (seconds)\",\n          \"timeout\": \"Timeout (seconds)\"\n        }\n      },\n      \"reconfigure\": {\n        \"title\": \"Hoymiles DTU connection\",\n        \"description\": \"If you need help with the configuration have a look here: https://github.com/suaveolent/ha-hoymiles-wifi\",\n        \"data\": {\n          \"host\": \"Host\",\n          \"update_interval\": \"Update Interval (seconds)\",\n          \"timeout\": \"Timeout (seconds)\"\n        }\n      }\n    },\n    \"error\": {\n      \"cannot_connect\": \"Failed to connect.\"\n    },\n    \"abort\": {\n      \"already_configured\": \"Already configured.\"\n    }\n  },\n  \"entity\": {\n    \"binary_sensor\": {\n      \"dtu\": {\n        \"name\": \"DTU\"\n      }\n    },\n    \"number\": {\n      \"limit_power_mypower\": {\n        \"name\": \"Power limit\"\n      }\n    },\n    \"sensor\": {\n      \"ac_active_power\": {\n        \"name\": \"AC power\"\n      },\n      \"ac_daily_energy\": {\n        \"name\": \"AC daily energy\"\n      },\n      \"ac_reactive_power\": {\n        \"name\": \"AC reactive power\"\n      },\n      \"grid_voltage\": {\n        \"name\": \"Grid voltage\"\n      },\n      \"ac_current\": {\n        \"name\": \"AC current\" \n      },\n      \"grid_frequency\": {\n        \"name\": \"Grid frequency\"\n      },\n      \"inverter_power_factor\": {\n        \"name\": \"Power factor\"\n      },\n      \"inverter_temperature\": {\n        \"name\": \"Temperature\"\n      },\n      \"inverter_warning_number\": {\n        \"name\": \"Warning number\"\n      },\n      \"port_dc_voltage\": {\n        \"name\": \"Port {port_number} DC voltage\"\n      },\n      \"port_dc_current\": {\n        \"name\": \"Port {port_number} DC current\"\n      },\n      \"port_dc_power\": {\n        \"name\": \"Port {port_number} DC power\"\n      },\n      \"port_dc_total_energy\": {\n        \"name\": \"Port {port_number} DC total energy\"\n      },\n      \"port_dc_daily_energy\": {\n        \"name\": \"Port {port_number} DC daily energy\"\n      },\n      \"port_error_code\": {\n        \"name\": \"Port {port_number} error code\"\n      },\n      \"wifi_ssid\": {\n        \"name\": \"Wi-Fi SSID\"\n      },\n      \"meter_kind\": {\n        \"name\": \"Meter kind\"\n      },\n      \"mac_address\": {\n        \"name\": \"MAC address\"\n      },\n      \"ip_address\": {\n        \"name\": \"IP address\"\n      },\n      \"dtu_ap_ssid\": {\n        \"name\": \"AP SSID\"\n      },\n      \"dtu_sw_version\": {\n        \"name\": \"SW version\"\n      },\n      \"dtu_hw_version\": {\n        \"name\": \"HW version\"\n      },\n      \"pv_sw_version\": {\n        \"name\": \"SW version\"\n      },\n      \"pv_hw_version\": {\n        \"name\": \"HW version\"\n      },\n      \"signal_strength\": {\n        \"name\": \"Signal strength\"\n      },\n      \"voltage_phase_A\": {\n        \"name\": \"Voltage phase A\"\n      },\n      \"voltage_phase_B\": {\n        \"name\": \"Voltage phase B\"\n      },\n      \"voltage_phase_C\": {\n        \"name\": \"Voltage phase C\"\n      },\n      \"voltage_line_AB\": {\n        \"name\": \"Voltage line AB\"\n      },\n      \"voltage_line_BC\": {\n        \"name\": \"Voltage line BC\"\n      },\n      \"voltage_line_CA\": {\n        \"name\": \"Voltage line CA\"\n      },\n      \"phase_total_power\": {\n        \"name\": \"Phase total power\"\n      },\n      \"phase_A_power\": {\n        \"name\": \"Phase A power\"\n      },\n      \"phase_B_power\": {\n        \"name\": \"Phase B power\"\n      },\n      \"phase_C_power\": {\n        \"name\": \"Phase C power\"\n      },\n      \"power_factor_total\": {\n        \"name\": \"Power factor total\"\n      },\n      \"energy_total_power\": {\n        \"name\": \"Energy total power\"\n      },\n      \"energy_phase_A\": {\n        \"name\": \"Energy phase A\"\n      },\n      \"energy_phase_B\": {\n        \"name\": \"Energy phase B\"\n      },\n      \"energy_phase_C\": {\n        \"name\": \"Energy phase C\"\n      },\n      \"energy_total_consumed\": {\n        \"name\": \"Energy total consumed\"\n      },\n      \"energy_phase_A_consumed\": {\n        \"name\": \"Energy phase A consumed\"\n      },\n      \"energy_phase_B_consumed\": {\n        \"name\": \"Energy phase B consumed\"\n      },\n      \"energy_phase_C_consumed\": {\n        \"name\": \"Energy phase C consumed\"\n      },\n      \"current_phase_A\": {\n        \"name\": \"Current phase A\"\n      },\n      \"current_phase_B\": {\n        \"name\": \"Current phase B\"\n      },\n      \"current_phase_C\": {\n        \"name\": \"Current phase C\"\n      },\n      \"power_factor_phase_A\": {\n        \"name\": \"Power factor phase A\"\n      },\n      \"power_factor_phase_B\": {\n        \"name\": \"Power factor phase B\"\n      },\n      \"power_factor_phase_C\": {\n        \"name\": \"Power factor phase C\"\n      },\n      \"energy_to_load\": {\n        \"name\": \"Energy to load\"\n      },\n      \"energy_to_battery\": {\n        \"name\": \"Energy to battery\"\n      },\n      \"energy_to_grid\": {\n        \"name\": \"Energy to grid\"\n      },\n      \"energy_from_pv\": {\n        \"name\": \"Energy from PV\"\n      },\n      \"energy_from_battery\": {\n        \"name\": \"Energy from battery\"\n      },\n      \"energy_from_grid\": {\n        \"name\": \"Energy from grid\"\n      },\n      \"pv_panel_voltage\": {\n        \"name\": \"PV panel {port_number} voltage\"\n      },\n      \"pv_panel_current\": {\n        \"name\": \"PV panel {port_number} current\"\n      },\n      \"pv_panel_power\": {\n        \"name\": \"PV panel {port_number} power\"\n      },\n      \"pv_panel_energy\": {\n        \"name\": \"PV panel {port_number} energy\"\n      },\n      \"state_of_charge\": {\n        \"name\": \"State of charge\"\n      },\n      \"state_of_health\": {\n        \"name\": \"State of health\"\n      },\n      \"battery_voltage\": {\n        \"name\": \"Battery voltage\"\n      },\n      \"internal_charge_mode\": {\n        \"name\": \"Internal charge mode\"\n      },\n      \"internal_discharge_mode\": {\n        \"name\": \"Internal discharge mode\"\n      },\n      \"cell_voltage_high\": {\n        \"name\": \"Cell voltage high\"\n      },\n      \"cell_voltage_low\": {\n        \"name\": \"Cell voltage low\"\n      },\n      \"temp_high_charge\": {\n        \"name\": \"Temperature high (charge)\"\n      },\n      \"temp_low_charge\": {\n        \"name\": \"Temperature low (charge)\"\n      },\n      \"temp_high_module\": {\n        \"name\": \"Module temperature high\"\n      },\n      \"temp_low_module\": {\n        \"name\": \"Module temperature low\"\n      },\n      \"energy_charged\": {\n        \"name\": \"Energy charged\"\n      },\n      \"energy_discharged\": {\n        \"name\": \"Energy discharged\"\n      },\n      \"voltage_charge_high\": {\n        \"name\": \"Voltage charge high\"\n      },\n      \"voltage_charge_low\": {\n        \"name\": \"Voltage charge low\"\n      },\n      \"voltage_module_high\": {\n        \"name\": \"Module voltage high\"\n      },\n      \"voltage_module_low\": {\n        \"name\": \"Module voltage low\"\n      },\n      \"grid_status\": {\n        \"name\": \"Grid status\"\n      },\n      \"grid_power_factor_deviation\": {\n        \"name\": \"Power factor deviation\"\n      },\n      \"grid_voltage_phase\": {\n        \"name\": \"Grid voltage phase {phase}\"\n      },\n      \"grid_current_phase\": {\n        \"name\": \"Grid current phase {phase}\"\n      },\n      \"grid_reactive_power_phase\": {\n        \"name\": \"Grid reactive power phase {phase}\"\n      },\n      \"grid_active_power_phase\": {\n        \"name\": \"Grid active power phase {phase}\"\n      },\n      \"grid_power_factor_phase\": {\n        \"name\": \"Grid power factor phase {phase}\"\n      },\n      \"grid_energy_frequency_phase\": {\n        \"name\": \"Grid energy frequency phase {phase}\"\n      },\n      \"grid_energy_consumed_phase\": {\n        \"name\": \"Grid energy consumed phase {phase}\"\n      },\n      \"load_status\": {\n        \"name\": \"Load status\"\n      },\n      \"load_frequency\": {\n        \"name\": \"Load frequency\"\n      },\n      \"load_voltage_phase\": {\n        \"name\": \"Load voltage phase {phase}\"\n      },\n      \"load_active_power_phase\": {\n        \"name\": \"Load active power phase {phase}\"\n      },\n      \"load_energy_consumed_phase\": {\n        \"name\": \"Load energy consumed phase {phase}\"\n      },\n      \"inverter_status\": {\n        \"name\": \"Inverter status\"\n      },\n      \"inverter_frequency\": {\n        \"name\": \"Inverter frequency\"\n      },\n      \"inverter_isolation_resistance\": {\n        \"name\": \"Inverter isolation resistance\"\n      },\n      \"inverter_leakage_current\": {\n        \"name\": \"Inverter leakage current\"\n      },\n      \"inverter_drm_signal\": {\n        \"name\": \"Inverter DRM signal\"\n      },\n      \"inverter_voltage_phase\": {\n        \"name\": \"Inverter voltage phase {phase}\"\n      },\n      \"inverter_current_phase\": {\n        \"name\": \"Inverter current phase {phase}\"\n      },\n      \"inverter_active_power_phase\": {\n        \"name\": \"Inverter active power phase {phase}\"\n      },\n      \"inverter_reactive_power_phase\": {\n        \"name\": \"Inverter reactive power phase {phase}\"\n      },\n      \"inverter_dc_current_phase\": {\n        \"name\": \"Inverter DC current phase {phase}\"\n      },\n      \"inverter_dc_voltage_phase\": {\n        \"name\": \"Inverter DC voltage phase {phase}\"\n      },\n      \"inverter_eps_voltage_phase\": {\n        \"name\": \"Inverter EPS voltage phase {phase}\"\n      },\n      \"inverter_eps_current_phase\": {\n        \"name\": \"Inverter EPS current phase {phase}\"\n      },\n      \"inverter_eps_power_phase\": {\n        \"name\": \"Inverter EPS power phase {phase}\"\n      },\n      \"pv_inverter_status\": {\n        \"name\": \"PV-Inverter status\"\n      },\n      \"pv_inverter_frequency\": {\n        \"name\": \"PV-Inverter frequency\"\n      },\n      \"pv_inverter_voltage_phase\": {\n        \"name\": \"PV-Inverter voltage phase {phase}\"\n      },\n      \"pv_inverter_current_phase\": {\n        \"name\": \"PV-Inverter current phase {phase}\"\n      },\n      \"pv_inverter_active_power_phase\": {\n        \"name\": \"PV-Inverter active power phase {phase}\"\n      },\n      \"pv_inverter_reactive_power_phase\": {\n        \"name\": \"PV-Inverter reactive power phase {phase}\"\n      },\n      \"pv_inverter_energy_phase\": {\n        \"name\": \"PV-Inverter energy phase {phase}\"\n      },\n      \"pv_to_load\": {\n        \"name\": \"Power PV to load\"\n      },\n      \"battery_to_load\": {\n        \"name\": \"Power battery to load\"\n      },\n      \"grid_to_load\": {\n        \"name\": \"Power grid to load\"\n      },\n      \"pv_to_battery\": {\n        \"name\": \"Power PV to battery\"\n      },\n      \"pv_to_grid\": {\n        \"name\": \"Power PV to grid\"\n      },\n      \"battery_to_grid\": {\n        \"name\": \"Power battery to grid\"\n      }\n    },\n    \"button\": {\n      \"restart\": {\n        \"name\": \"Restart\"\n      },\n      \"turn_off\": {\n        \"name\": \"Turn off\"\n      },\n      \"turn_on\": {\n        \"name\": \"Turn on\"\n      },\n      \"enable_performance_data_mode\": {\n        \"name\": \"Experimental: Enable performance data mode\"\n      }\n    }\n  },\n  \"device\": {\n    \"inverter\": {\n      \"name\": \"Inverter\"\n    },\n    \"dtu\": {\n      \"name\": \"DTU\"\n    },\n    \"meter\": {\n      \"name\": \"Meter\"\n    },\n    \"hybrid_inverter\": {\n      \"name\": \"Hybrid inverter\"\n    }\n  },\n  \"services\": {\n    \"set_bms_mode\": {\n      \"name\": \"Set BMS mode\",\n      \"description\": \"Set the BMS mode of the connected battery.\",\n      \"fields\": {\n        \"bms_mode\": {\n          \"name\": \"BMS mode\",\n          \"description\": \"The BMS working mode to set. Possible values are: 'self_use', 'economic', 'backup_power', 'pure_off_grid', 'forced_charging', 'forced_discharge', 'peak_shaving', 'time_of_use'\"\n        },\n        \"rev_soc\": {\n          \"name\": \"Reserved SOC\",\n          \"description\": \"The reserved state of charge (SOC) to set (in %).\"\n        },\n        \"max_power\": {\n          \"name\": \"Max Power\",\n          \"description\": \"The maximum charge/discharge power to set (in %).\"\n        },\n        \"peak_soc\": {\n          \"name\": \"Peak SOC\",\n          \"description\": \"The peak state of charge (SOC) to set (in W).\"\n        },\n        \"peak_meter_power\": {\n          \"name\": \"Peak Meter Power\",\n          \"description\": \"The peak meter power to set (in W).\"\n        },\n        \"time_settings\": {\n          \"name\": \"Time Settings\",\n          \"description\": \"Configure time-of-use settings.\"\n        },\n        \"time_periods\": {\n          \"name\": \"Time Periods\",\n          \"description\": \"Define time periods for time-of-use mode.\"\n        }\n      }\n    }\n  },\n  \"selector\": {\n    \"bms_mode_type\": {\n      \"options\": {\n        \"self_use\": \"Self-Consumption Mode\",\n        \"economic\": \"Economy Mode\",\n        \"backup_power\": \"Backup Mode\",\n        \"pure_off_grid\": \"Off-Grid Mode\",\n        \"forced_charging\": \"Force Charge Mode\",\n        \"forced_discharge\": \"Force Discharge Mode\",\n        \"peak_shaving\": \"Peak Shaving Mode\",\n        \"time_of_use\": \"Time of Use Mode\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/translations/fr.json",
    "content": "{\r\n  \"config\": {\r\n    \"step\": {\r\n      \"user\": {\r\n        \"title\": \"Connexion DTU Hoymiles\",\r\n        \"description\": \"Si vous avez besoin d'aide pour la configuration, consultez ici : https://github.com/suaveolent/ha-hoymiles-wifi\",\r\n        \"data\": {\r\n          \"host\": \"Hôte\",\r\n          \"update_interval\": \"Intervalle de mise à jour (secondes)\",\r\n          \"timeout\": \"Délai d'attente (secondes)\"\r\n        }\r\n      },\r\n      \"reconfigure\": {\r\n        \"title\": \"Connexion DTU Hoymiles\",\r\n        \"description\": \"Si vous avez besoin d'aide pour la configuration, consultez ici : https://github.com/suaveolent/ha-hoymiles-wifi\",\r\n        \"data\": {\r\n          \"host\": \"Hôte\",\r\n          \"update_interval\": \"Intervalle de mise à jour (secondes)\",\r\n          \"timeout\": \"Délai d'attente (secondes)\"\r\n        }\r\n      }\r\n    },\r\n    \"error\": {\r\n      \"cannot_connect\": \"Échec de la connexion.\"\r\n    },\r\n    \"abort\": {\r\n      \"already_configured\": \"Déjà configuré.\"\r\n    }\r\n  },\r\n  \"entity\": {\r\n    \"binary_sensor\": {\r\n      \"dtu\": {\r\n        \"name\": \"DTU\"\r\n      }\r\n    },\r\n    \"number\": {\r\n      \"limit_power_mypower\": {\r\n        \"name\": \"Limite de puissance\"\r\n      }\r\n    },\r\n    \"sensor\": {\r\n      \"ac_active_power\": {\r\n        \"name\": \"Puissance AC\"\r\n      },\r\n      \"ac_daily_energy\": {\r\n        \"name\": \"Énergie quotidienne AC\"\r\n      },\r\n      \"ac_reactive_power\": {\r\n        \"name\": \"Puissance réactive AC\"\r\n      },\r\n      \"grid_voltage\": {\r\n        \"name\": \"Tension du réseau\"\r\n      },\r\n      \"ac_current\": {\r\n        \"name\": \"Courant AC\" \r\n      },\r\n      \"grid_frequency\": {\r\n        \"name\": \"Fréquence du réseau\"\r\n      },\r\n      \"inverter_power_factor\": {\r\n        \"name\": \"Facteur de puissance de l'onduleur\"\r\n      },\r\n      \"inverter_temperature\": {\r\n        \"name\": \"Température de l'onduleur\"\r\n      },\r\n      \"inverter_warning_number\": {\r\n        \"name\": \"Numéro d'avertissement\"\r\n      },\r\n      \"port_dc_voltage\": {\r\n        \"name\": \"Tension DC du port {port_number}\"\r\n      },\r\n      \"port_dc_current\": {\r\n        \"name\": \"Courant DC du port {port_number}\"\r\n      },\r\n      \"port_dc_power\": {\r\n        \"name\": \"Puissance DC du port {port_number}\"\r\n      },\r\n      \"port_dc_total_energy\": {\r\n        \"name\": \"Énergie totale DC du port {port_number}\"\r\n      },\r\n      \"port_dc_daily_energy\": {\r\n        \"name\": \"Énergie quotidienne DC du port {port_number}\"\r\n      },\r\n      \"port_error_code\": {\r\n        \"name\": \"Code d'erreur du port {port_number}\"\r\n      },\r\n      \"wifi_ssid\": {\r\n        \"name\": \"SSID Wi-Fi\"\r\n      },\r\n      \"meter_kind\": {\r\n        \"name\": \"Type de compteur\"\r\n      },\r\n      \"mac_address\": {\r\n        \"name\": \"Adresse MAC\"\r\n      },\r\n      \"ip_address\": {\r\n        \"name\": \"Adresse IP\"\r\n      },\r\n      \"dtu_ap_ssid\": {\r\n        \"name\": \"SSID AP\"\r\n      },\r\n      \"dtu_sw_version\": {\r\n        \"name\": \"Version SW\"\r\n      },\r\n      \"dtu_hw_version\": {\r\n        \"name\": \"Version HW\"\r\n      },\r\n      \"pv_sw_version\": {\r\n        \"name\": \"Version SW\"\r\n      },\r\n      \"pv_hw_version\": {\r\n        \"name\": \"Version HW\"\r\n      },\r\n      \"signal_strength\": {\r\n        \"name\": \"Force du signal\"\r\n      },\r\n      \"voltage_phase_A\": {\r\n        \"name\": \"Tension phase A\"\r\n      },\r\n      \"voltage_phase_B\": {\r\n        \"name\": \"Tension phase B\"\r\n      },\r\n      \"voltage_phase_C\": {\r\n        \"name\": \"Tension phase C\"\r\n      },\r\n      \"voltage_line_AB\": {\r\n        \"name\": \"Tension ligne AB\"\r\n      },\r\n      \"voltage_line_BC\": {\r\n        \"name\": \"Tension ligne BC\"\r\n      },\r\n      \"voltage_line_CA\": {\r\n        \"name\": \"Tension ligne CA\"\r\n      },\r\n      \"phase_total_power\": {\r\n        \"name\": \"Puissance totale de phase\"\r\n      },\r\n      \"phase_A_power\": {\r\n        \"name\": \"Puissance phase A\"\r\n      },\r\n      \"phase_B_power\": {\r\n        \"name\": \"Puissance phase A\"\r\n      },\r\n      \"phase_C_power\": {\r\n        \"name\": \"Puissance phase A\"\r\n      },\r\n      \"power_factor_total\": {\r\n        \"name\": \"Facteur de puissance total\"\r\n      },\r\n      \"energy_total_power\": {\r\n        \"name\": \"Énergie totale de puissance\"\r\n      },\r\n      \"energy_phase_A\": {\r\n        \"name\": \"Énergie phase A\"\r\n      },\r\n      \"energy_phase_B\": {\r\n        \"name\": \"Énergie phase B\"\r\n      },\r\n      \"energy_phase_C\": {\r\n        \"name\": \"Énergie phase C\"\r\n      },\r\n      \"energy_total_consumed\": {\r\n        \"name\": \"Énergie totale consommée\"\r\n      },\r\n      \"energy_phase_A_consumed\": {\r\n        \"name\": \"Énergie phase A consommée\"\r\n      },\r\n      \"energy_phase_B_consumed\": {\r\n        \"name\": \"Énergie phase B consommée\"\r\n      },\r\n      \"energy_phase_C_consumed\": {\r\n        \"name\": \"Énergie phase C consommée\"\r\n      },\r\n      \"current_phase_A\": {\r\n        \"name\": \"Courant phase A\"\r\n      },\r\n      \"current_phase_B\": {\r\n        \"name\": \"Courant phase B\"\r\n      },\r\n      \"current_phase_C\": {\r\n        \"name\": \"Courant phase C\"\r\n      },\r\n      \"power_factor_phase_A\": {\r\n        \"name\": \"Facteur de puissance phase A\"\r\n      }, \r\n      \"power_factor_phase_B\": {\r\n        \"name\": \"Facteur de puissance phase B\"\r\n      },\r\n      \"power_factor_phase_C\": {\r\n        \"name\": \"Facteur de puissance phase C\"\r\n      },\r\n      \"energy_to_load\": {\r\n        \"name\": \"Énergie à charger\"\r\n      },\r\n      \"energy_to_battery\": {\r\n        \"name\": \"Énergie à la batterie\"\r\n      },\r\n      \"energy_to_grid\": {\r\n        \"name\": \"Énergie vers le réseau\"\r\n      },\r\n      \"energy_from_pv\": {\r\n        \"name\": \"Énergie provenant de PV\"\r\n      },\r\n      \"energy_from_battery\": {\r\n        \"name\": \"Énergie provenant de la batterie\"\r\n      },\r\n      \"energy_from_grid\": {\r\n        \"name\": \"Énergie provenant du réseau\"\r\n      },\r\n      \"pv_panel_voltage\": {\r\n        \"name\": \"Tension du panneau PV {port_number}\"\r\n      },\r\n      \"pv_panel_current\": {\r\n        \"name\": \"Courant du panneau PV {port_number}\"\r\n      },\r\n      \"pv_panel_power\": {\r\n        \"name\": \"Puissance du panneau PV {port_number}\"\r\n      },\r\n      \"pv_panel_energy\": {\r\n        \"name\": \"Énergie du panneau PV {port_number}\"\r\n      },\r\n      \"state_of_charge\": {\r\n        \"name\": \"État de charge\"\r\n      },\r\n      \"state_of_health\": {\r\n        \"name\": \"État de santé\"\r\n      },\r\n      \"battery_voltage\": {\r\n        \"name\": \"Tension de la batterie\"\r\n      },\r\n      \"internal_charge_mode\": {\r\n        \"name\": \"Mode de charge interne\"\r\n      },\r\n      \"internal_discharge_mode\": {\r\n        \"name\": \"Mode de décharge interne\"\r\n      },\r\n      \"cell_voltage_high\": {\r\n        \"name\": \"Tension maximale des cellules\"\r\n      },\r\n      \"cell_voltage_low\": {\r\n        \"name\": \"Tension minimale des cellules\"\r\n      },\r\n      \"temp_high_charge\": {\r\n        \"name\": \"Température maximale de charge\"\r\n      },\r\n      \"temp_low_charge\": {\r\n        \"name\": \"Température minimale de charge\"\r\n      },\r\n      \"temp_high_module\": {\r\n        \"name\": \"Température maximale du module\"\r\n      },\r\n      \"temp_low_module\": {\r\n        \"name\": \"Température minimale du module\"\r\n      },\r\n      \"energy_charged\": {\r\n        \"name\": \"Énergie chargée\"\r\n      },\r\n      \"energy_discharged\": {\r\n        \"name\": \"Énergie déchargée\"\r\n      },\r\n      \"voltage_charge_high\": {\r\n        \"name\": \"Tension de charge maximale\"\r\n      },\r\n      \"voltage_charge_low\": {\r\n        \"name\": \"Tension de charge minimale\"\r\n      },\r\n      \"voltage_module_high\": {\r\n        \"name\": \"Tension maximale du module\"\r\n      },\r\n      \"voltage_module_low\": {\r\n        \"name\": \"Tension minimale du module\"\r\n      },\r\n      \"grid_status\": {\r\n        \"name\": \"État du réseau\"\r\n      },\r\n      \"grid_power_factor_deviation\": {\r\n        \"name\": \"Écart du facteur de puissance\"\r\n      },\r\n      \"grid_voltage_phase\": {\r\n        \"name\": \"Tension réseau phase {phase}\"\r\n      },\r\n      \"grid_current_phase\": {\r\n        \"name\": \"Courant réseau phase {phase}\"\r\n      },\r\n      \"grid_reactive_power_phase\": {\r\n        \"name\": \"Puissance réactive phase {phase}\"\r\n      },\r\n      \"grid_active_power_phase\": {\r\n        \"name\": \"Puissance active du réseau phase {phase}\"\r\n      },\r\n      \"grid_power_factor_phase\": {\r\n        \"name\": \"Facteur de puissance phase {phase}\"\r\n      },\r\n      \"grid_energy_frequency_phase\": {\r\n        \"name\": \"Fréquence énergie phase {phase}\"\r\n      },\r\n      \"grid_energy_consumed_phase\": {\r\n        \"name\": \"Énergie consommée phase {phase}\"\r\n      },\r\n      \"load_status\": {\r\n        \"name\": \"État de la charge\"\r\n      },\r\n      \"load_frequency\": {\r\n        \"name\": \"Fréquence de la charge\"\r\n      },\r\n      \"load_voltage_phase\": {\r\n        \"name\": \"Tension charge phase {phase}\"\r\n      },\r\n      \"load_active_power_phase\": {\r\n        \"name\": \"Puissance active phase {phase}\"\r\n      },\r\n      \"load_energy_consumed_phase\": {\r\n        \"name\": \"Énergie consommée phase {phase}\"\r\n      },\r\n      \"inverter_status\": {\r\n        \"name\": \"État de l'onduleur\"\r\n      },\r\n      \"inverter_frequency\": {\r\n         \"name\": \"Fréquence de l'onduleur\"\r\n      },\r\n      \"inverter_isolation_resistance\": {\r\n        \"name\": \"Résistance d'isolement de l'onduleur\"\r\n       },\r\n      \"inverter_leakage_current\": {\r\n        \"name\": \"Courant de fuite de l'onduleur\"\r\n      },\r\n      \"inverter_drm_signal\": {\r\n        \"name\": \"Signal DRM de l'onduleur\"\r\n      },\r\n      \"inverter_voltage_phase\": {\r\n        \"name\": \"Tension de l'onduleur phase {phase}\"\r\n      },\r\n      \"inverter_current_phase\": {\r\n        \"name\": \"Courant de l'onduleur phase {phase}\"\r\n      },\r\n      \"inverter_active_power_phase\": {\r\n        \"name\": \"Puissance active onduleur phase {phase}\"\r\n      },\r\n      \"inverter_reactive_power_phase\": {\r\n        \"name\": \"Puissance réactive onduleur phase {phase}\"\r\n      },\r\n      \"inverter_dc_current_phase\": {\r\n         \"name\": \"Courant DC onduleur phase {phase}\"\r\n      },\r\n      \"inverter_dc_voltage_phase\": {\r\n         \"name\": \"Tension DC onduleur phase {phase}\"\r\n      },\r\n      \"inverter_eps_voltage_phase\": {\r\n         \"name\": \"Tension EPS onduleur phase {phase}\"\r\n       },\r\n      \"inverter_eps_current_phase\": {\r\n         \"name\": \"Courant EPS onduleur phase {phase}\"\r\n      },\r\n      \"inverter_eps_power_phase\": {\r\n         \"name\": \"Puissance EPS onduleur phase {phase}\"\r\n      },\r\n      \"pv_inverter_status\": {\r\n        \"name\": \"État de l'onduleur PV\"\r\n      },\r\n      \"pv_inverter_frequency\": {\r\n        \"name\": \"Fréquence de l'onduleur PV\"\r\n      },\r\n      \"pv_inverter_voltage_phase\": {\r\n        \"name\": \"Tension de phase de l'onduleur PV {phase}\"\r\n      },\r\n      \"pv_inverter_current_phase\": {\r\n        \"name\": \"Courant de phase de l'onduleur PV {phase}\"\r\n      },\r\n      \"pv_inverter_active_power_phase\": {\r\n        \"name\": \"Puissance active de phase de l'onduleur PV {phase}\"\r\n      },\r\n      \"pv_inverter_reactive_power_phase\": {\r\n        \"name\": \"Puissance réactive de phase de l'onduleur PV {phase}\"\r\n      },\r\n      \"pv_inverter_energy_phase\": {\r\n        \"name\": \"Énergie de phase de l'onduleur PV {phase}\"\r\n      },\r\n      \"pv_to_load\": {\r\n        \"name\": \"Puissance PV vers charge\"\r\n      },\r\n      \"battery_to_load\": {\r\n        \"name\": \"Puissance batterie vers charge\"\r\n      },\r\n      \"grid_to_load\": {\r\n        \"name\": \"Puissance réseau vers charge\"\r\n      },\r\n      \"pv_to_battery\": {\r\n        \"name\": \"Puissance PV vers batterie\"\r\n      },\r\n      \"pv_to_grid\": {\r\n        \"name\": \"Puissance PV vers réseau\"\r\n      },\r\n      \"battery_to_grid\": {\r\n        \"name\": \"Puissance batterie vers réseau\"\r\n      }\r\n    },\r\n    \"button\": {\r\n      \"restart\": {\r\n        \"name\": \"Redémarrer\"\r\n      },\r\n      \"turn_off\": {\r\n        \"name\": \"Éteindre\"\r\n      },\r\n      \"turn_on\": {\r\n        \"name\": \"Allumer\"\r\n      },\r\n      \"enable_performance_data_mode\": {\r\n        \"name\": \"Expérimental: Activer le mode de données de performance\"\r\n      }\r\n    }\r\n  },\r\n  \"device\": {\r\n    \"inverter\": {\r\n      \"name\": \"Onduleur\"\r\n    },\r\n    \"dtu\": {\r\n      \"name\": \"DTU\"\r\n    },\r\n    \"meter\": {\r\n      \"name\": \"Compteur\"\r\n    },\r\n    \"hybrid_inverter\": {\r\n      \"name\": \"Onduleur hybride\"\r\n    }\r\n  },\r\n  \"services\": {\r\n    \"set_bms_mode\": {\r\n      \"name\": \"Définir le mode BMS\",\r\n      \"description\": \"Définir le mode BMS de la batterie connectée.\",\r\n      \"fields\": {\r\n        \"bms_mode\": {\r\n          \"name\": \"Mode BMS\",\r\n          \"description\": \"Le mode de fonctionnement BMS à définir. Valeurs possibles : 'self_use', 'economic', 'backup_power', 'pure_off_grid', 'forced_charging', 'forced_discharge', 'peak_shaving', 'time_of_use'\"\r\n        },\r\n        \"rev_soc\": {\r\n          \"name\": \"SOC réservé\",\r\n          \"description\": \"L’état de charge (SOC) réservé à définir (en %).\"\r\n        },\r\n        \"max_power\": {\r\n          \"name\": \"Puissance maximale\",\r\n          \"description\": \"La puissance maximale de charge/décharge à définir (en %).\"\r\n        },\r\n        \"peak_soc\": {\r\n          \"name\": \"SOC de pointe\",\r\n          \"description\": \"L’état de charge (SOC) de pointe à définir (en %).\"\r\n        },\r\n        \"peak_meter_power\": {\r\n          \"name\": \"Puissance de pointe du compteur\",\r\n          \"description\": \"La puissance de pointe du compteur à définir (en W).\"\r\n        },\r\n        \"time_settings\": {\r\n          \"name\": \"Paramètres temporels\",\r\n          \"description\": \"Configurer les paramètres liés au mode heures d’utilisation.\"\r\n        },\r\n        \"time_periods\": {\r\n          \"name\": \"Périodes horaires\",\r\n          \"description\": \"Définir les périodes pour le mode heures d’utilisation.\"\r\n        }\r\n      }\r\n    }\r\n  },\r\n  \"selector\": {\r\n    \"bms_mode_type\": {\r\n      \"options\": {\r\n        \"self_use\": \"Mode autoconsommation\",\r\n        \"economic\": \"Mode économique\",\r\n        \"backup_power\": \"Mode secours\",\r\n        \"pure_off_grid\": \"Mode hors réseau\",\r\n        \"forced_charging\": \"Mode charge forcée\",\r\n        \"forced_discharge\": \"Mode décharge forcée\",\r\n        \"peak_shaving\": \"Mode écrêtage de pointe\",\r\n        \"time_of_use\": \"Mode heures d’utilisation\"\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "custom_components/hoymiles_wifi/util.py",
    "content": "\"\"\"Utils for hoymiles-wifi.\"\"\"\n\nfrom typing import Union\nimport asyncio\nimport logging\n\nfrom hoymiles_wifi.dtu import DTU\nfrom hoymiles_wifi.hoymiles import generate_inverter_serial_number\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.core import HomeAssistant\n\nfrom hoymiles_wifi.const import IS_ENCRYPTED_BIT_INDEX\n\nfrom .error import CannotConnect\n\nfrom .const import CONF_ENC_RAND, DEFAULT_TIMEOUT_SECONDS\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_get_config_entry_data_for_host(\n    host,\n) -> tuple[\n    str,\n    list[str],\n    list[dict[str, Union[str, int]]],\n    list[dict[str, Union[str, int]]],\n    list[dict[str, Union[str, int]]],\n    bool,\n    str,\n]:\n    \"\"\"Get data for config entry from host.\"\"\"\n\n    single_phase_inverters = []\n    three_phase_inverters = []\n    ports = []\n    meters = []\n    hybrid_inverters = []\n    dtu_sn = None\n    is_encrypted = False\n    enc_rand = \"\"\n\n    dtu = DTU(host, timeout=DEFAULT_TIMEOUT_SECONDS)\n\n    app_information_data = await dtu.async_app_information_data()\n\n    if app_information_data and app_information_data.dtu_info.dfs:\n        if is_encrypted_dtu(app_information_data.dtu_info.dfs):\n            logging.debug(\"DTU is encrypted.\")\n            is_encrypted = True\n            enc_rand = app_information_data.dtu_info.enc_rand.hex()\n            dtu = DTU(\n                host,\n                is_encrypted=is_encrypted,\n                enc_rand=bytes.fromhex(enc_rand),\n                timeout=DEFAULT_TIMEOUT_SECONDS,\n            )\n            await asyncio.sleep(2)\n\n    logging.debug(\"Trying get_real_data_new()!\")\n    real_data = await dtu.async_get_real_data_new()\n    logging.debug(f\"RealDataNew call done. Result: {real_data}\")\n\n    if real_data:\n        dtu_sn = real_data.device_serial_number\n\n        single_phase_inverters = [\n            generate_inverter_serial_number(sgs_data.serial_number)\n            for sgs_data in real_data.sgs_data\n        ]\n\n        three_phase_inverters = [\n            generate_inverter_serial_number(tgs_data.serial_number)\n            for tgs_data in real_data.tgs_data\n        ]\n\n        ports = [\n            {\n                \"inverter_serial_number\": generate_inverter_serial_number(\n                    pv_data.serial_number\n                ),\n                \"port_number\": pv_data.port_number,\n            }\n            for pv_data in real_data.pv_data\n        ]\n\n        meters = [\n            {\n                \"meter_serial_number\": generate_inverter_serial_number(\n                    meter_data.serial_number\n                ),\n                \"device_type\": meter_data.device_type,\n            }\n            for meter_data in real_data.meter_data\n        ]\n    else:\n        logging.debug(\n            \"RealDataNew is None. Sleeping for 5s before trying get_gateway_info()!\"\n        )\n        await asyncio.sleep(5)\n        gateway_info = await dtu.async_get_gateway_info()\n        logging.debug(f\"GatewayInfo call done. Result: {gateway_info}\")\n\n        if gateway_info:\n            logging.debug(\"Trying get energy storage registry call.\")\n            registry = await dtu.async_get_energy_storage_registry(\n                dtu_serial_number=gateway_info.serial_number\n            )\n            logging.debug(f\"Get energy storage registry call done. Result: {registry}\")\n\n            if registry:\n                dtu_sn = str(gateway_info.serial_number)\n\n                hybrid_inverters = [\n                    {\n                        \"inverter_serial_number\": inverter.serial_number,\n                        \"model_name\": inverter.model_name,\n                    }\n                    for inverter in registry.inverters\n                ]\n                logging.debug(f\"Hybrid inverters: {hybrid_inverters}\")\n            else:\n                logging.error(\n                    \"Energy storage registry is None. Cannot connect to DTU or invalid response received!\"\n                )\n                raise CannotConnect\n        else:\n            logging.error(\n                \"RealDataNew and GatewayInfo is None. Cannot connect to DTU or invalid response received!\"\n            )\n            raise CannotConnect\n\n    return (\n        dtu_sn,\n        single_phase_inverters,\n        three_phase_inverters,\n        ports,\n        meters,\n        hybrid_inverters,\n        is_encrypted,\n        enc_rand,\n    )\n\n\ndef is_encrypted_dtu(dfs: int) -> bool:\n    \"\"\"Check if the DTU is encrypted.\"\"\"\n    return (dfs >> IS_ENCRYPTED_BIT_INDEX) & 1\n\n\nasync def async_check_and_update_enc_rand(\n    hass: HomeAssistant, config_entry: ConfigEntry, dtu: DTU, enc_rand: str\n) -> None:\n    \"\"\"Check and update the enc_rand if necessary.\"\"\"\n    enc_rand_old = config_entry.data.get(CONF_ENC_RAND, None)\n\n    if enc_rand_old is None or enc_rand_old != enc_rand:\n        _LOGGER.debug(\n            \"Updating enc_rand in config entry and DTU from %s to %s\",\n            enc_rand_old,\n            enc_rand,\n        )\n        dtu.enc_rand = bytes.fromhex(enc_rand)\n        new_data = {**config_entry.data, CONF_ENC_RAND: enc_rand}\n        await hass.config_entries.async_update_entry(config_entry, data=new_data)\n"
  },
  {
    "path": "hacs.json",
    "content": "{\n  \"name\": \"Hoymiles\",\n  \"render_readme\": true,\n  \"iot_class\": \"local_polling\",\n  \"homeassistant\": \"2025.6.0\"\n}\n"
  },
  {
    "path": "requirements.test.txt",
    "content": "pytest\npytest-cov>=4.1.0\npytest-asyncio>=0.23.5\npytest-homeassistant-custom-component\nhoymiles-wifi>=0.2.1\n\n"
  },
  {
    "path": "setup.cfg",
    "content": "[coverage:run]\nsource =\n  custom_components\n\n[coverage:report]\nexclude_lines =\n    pragma: no cover\n    raise NotImplemented()\n    if __name__ == '__main__':\n    main()\nshow_missing = true\n\n[tool:pytest]\nasyncio_mode = auto\ntestpaths = tests\nnorecursedirs = .git\naddopts =\n    --strict\n    --cov=custom_components\n\n[flake8]\n# https://github.com/ambv/black#line-length\nmax-line-length = 88\n# E501: line too long\n# W503: Line break occurred before a binary operator\n# E203: Whitespace before ':'\n# D202 No blank lines allowed after function docstring\n# W504 line break after binary operator\nignore =\n    E501,\n    W503,\n    E203,\n    D202,\n    W504\n\n[isort]\n# https://github.com/timothycrosley/isort\n# https://github.com/timothycrosley/isort/wiki/isort-Settings\n# splits long import on multiple lines indented by 4 spaces\nmulti_line_output = 3\ninclude_trailing_comma=True\nforce_grid_wrap=0\nuse_parentheses=True\nline_length=88\nindent = \"    \"\n# by default isort don't check module indexes\nnot_skip = __init__.py\n# will group `import x` and `from x import` of the same module.\nforce_sort_within_sections = true\nsections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER\ndefault_section = THIRDPARTY\nknown_first_party = custom_components,tests\nforced_separate = tests\ncombine_as_imports = true\n\n[mypy]\npython_version = 3.10\nignore_errors = true\nfollow_imports = silent\nignore_missing_imports = true\nwarn_incomplete_stub = true\nwarn_redundant_casts = true\nwarn_unused_configs = true\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/bandit.yaml",
    "content": "# https://bandit.readthedocs.io/en/latest/config.html\n\ntests:\n  - B108\n  - B306\n  - B307\n  - B313\n  - B314\n  - B315\n  - B316\n  - B317\n  - B318\n  - B319\n  - B320\n  - B325\n  - B602\n  - B604\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "\"\"\"Global fixtures for hoymiles_wifi integration.\"\"\"\n\n# Fixtures allow you to replace functions with a Mock object. You can perform\n# many options via the Mock to reflect a particular behavior from the original\n# function that you want to see without going through the function's actual logic.\n# Fixtures can either be passed into tests as parameters, or if autouse=True, they\n# will automatically be used across all tests.\n#\n# Fixtures that are defined in conftest.py are available across all tests. You can also\n# define fixtures within a particular test file to scope them locally.\n#\n# pytest_homeassistant_custom_component provides some fixtures that are provided by\n# Home Assistant core. You can find those fixture definitions here:\n# https://github.com/MatthewFlamm/pytest-homeassistant-custom-component/blob/master/pytest_homeassistant_custom_component/common.py\n#\n# See here for more info: https://docs.pytest.org/en/latest/fixture.html (note that\n# pytest includes fixtures OOB which you can use as defined on this page)\nimport pytest\n\npytest_plugins = \"pytest_homeassistant_custom_component\"\n\n\n@pytest.fixture(autouse=True)\ndef auto_enable_custom_integrations(enable_custom_integrations):\n    \"\"\"Enable custom integrations\"\"\"\n    yield\n"
  },
  {
    "path": "tests/test_config_flow.py",
    "content": "\"\"\"Unit tests for the Hoymiles config flow.\"\"\"\n\nfrom json import JSONDecodeError\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom homeassistant import config_entries\nfrom custom_components.hoymiles_wifi.const import (\n    DOMAIN,\n    CONF_UPDATE_INTERVAL,\n    CONF_INVERTERS,\n    CONF_PORTS,\n    CONF_DTU_SERIAL_NUMBER,\n    DEFAULT_UPDATE_INTERVAL_SECONDS,\n)\nfrom custom_components.hoymiles_wifi.error import CannotConnect\n\nfrom homeassistant.const import CONF_HOST\nfrom homeassistant.core import HomeAssistant\nfrom homeassistant.data_entry_flow import FlowResultType\n\nfrom pytest_homeassistant_custom_component.common import MockConfigEntry\n\nfrom hoymiles_wifi.protobuf import (\n    RealDataNew_pb2,\n)\n\nDTU_TEST_HOST = \"DTUBI-123456789101.lan\"\n\nDTU_TEST_SERIAL_NUMBER = \"414312345678\"\n\nMOCK_DATA_STEP = {\n    CONF_HOST: DTU_TEST_HOST,\n    CONF_UPDATE_INTERVAL: DEFAULT_UPDATE_INTERVAL_SECONDS,\n}\n\nMOCK_DATA_RESULT = {\n    CONF_HOST: DTU_TEST_HOST,\n    CONF_DTU_SERIAL_NUMBER: DTU_TEST_SERIAL_NUMBER,\n    CONF_UPDATE_INTERVAL: DEFAULT_UPDATE_INTERVAL_SECONDS,\n    CONF_INVERTERS: [],\n    CONF_PORTS: [],\n}\n\n\nMOCK_DATA_REAL_DATA_NEW = RealDataNew_pb2.RealDataNewReqDTO()\nMOCK_DATA_REAL_DATA_NEW.device_serial_number = DTU_TEST_SERIAL_NUMBER\n\n\nasync def test_form_valid_input(hass: HomeAssistant) -> None:\n    \"\"\"Test handling valid user input.\"\"\"\n\n    result = await hass.config_entries.flow.async_init(\n        DOMAIN, context={\"source\": config_entries.SOURCE_USER}\n    )\n    assert result[\"type\"] == FlowResultType.FORM\n    assert result[\"errors\"] == {}\n\n    with (\n        patch(\n            \"custom_components.hoymiles_wifi.async_setup_entry\",\n            return_value=True,\n        ) as mock_setup_entry,\n        patch(\n            \"hoymiles_wifi.dtu.DTU.async_get_real_data_new\",\n            return_value=MOCK_DATA_REAL_DATA_NEW,\n        ) as mock_async_get_real_data_new,\n    ):\n        result2 = await hass.config_entries.flow.async_configure(\n            result[\"flow_id\"],\n            MOCK_DATA_STEP,\n        )\n    await hass.async_block_till_done()\n\n    assert result2[\"type\"] == FlowResultType.CREATE_ENTRY\n    assert result2[\"title\"] == MOCK_DATA_STEP[CONF_HOST]\n    assert result2[\"data\"] == MOCK_DATA_RESULT\n    assert len(mock_setup_entry.mock_calls) == 1\n    assert len(mock_async_get_real_data_new.mock_calls) == 1\n\n\n@pytest.mark.parametrize(\n    (\"raise_error\", \"text_error\"),\n    [\n        (CannotConnect(\"Test hoymiles exception\"), \"cannot_connect\"),\n    ],\n)\nasync def test_flow_user_init_data_error_and_recover(\n    hass: HomeAssistant, raise_error, text_error\n) -> None:\n    \"\"\"Test exceptions and recovery.\"\"\"\n    result = await hass.config_entries.flow.async_init(\n        DOMAIN, context={\"source\": config_entries.SOURCE_USER}\n    )\n    assert result[\"type\"] == FlowResultType.FORM\n    assert result[\"errors\"] == {}\n\n    with patch(\n        \"custom_components.hoymiles_wifi.util.DTU.async_get_real_data_new\",\n        side_effect=raise_error,\n    ) as mock_async_get_config_entry_data_for_host:\n        result2 = await hass.config_entries.flow.async_configure(\n            result[\"flow_id\"],\n            MOCK_DATA_STEP,\n        )\n        await hass.async_block_till_done()\n\n    assert result2[\"type\"] == FlowResultType.FORM\n    assert result2[\"errors\"] == {\"base\": text_error}\n\n    assert len(mock_async_get_config_entry_data_for_host.mock_calls) == 1\n\n    # Recover\n    with (\n        patch(\n            \"custom_components.hoymiles_wifi.async_setup_entry\",\n            return_value=True,\n        ) as mock_setup_entry,\n        patch(\n            \"hoymiles_wifi.dtu.DTU.async_get_real_data_new\",\n            return_value=MOCK_DATA_REAL_DATA_NEW,\n        ) as mock_async_get_real_data_new,\n    ):\n        result3 = await hass.config_entries.flow.async_configure(\n            result[\"flow_id\"],\n            MOCK_DATA_STEP,\n        )\n\n    await hass.async_block_till_done()\n\n    assert result3[\"type\"] == FlowResultType.CREATE_ENTRY\n    assert result3[\"title\"] == MOCK_DATA_STEP[CONF_HOST]\n    assert result3[\"data\"] == MOCK_DATA_RESULT\n    assert len(mock_setup_entry.mock_calls) == 1\n    assert len(mock_async_get_real_data_new.mock_calls) == 1\n"
  },
  {
    "path": "tests/test_init.py",
    "content": "\"\"\"Test component setup.\"\"\"\n\nfrom homeassistant.setup import async_setup_component\n\nfrom custom_components.hoymiles_wifi.const import DOMAIN\n\n\nasync def test_async_setup(hass):\n    \"\"\"Test the component gets setup.\"\"\"\n\n    assert await async_setup_component(hass, DOMAIN, {}) is True\n"
  }
]