[
  {
    "path": ".github/workflows/black.yml",
    "content": "name: Lint\n\non: [push, pull_request]\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: psf/black@stable"
  },
  {
    "path": ".github/workflows/python-app.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a single version of Python\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: Check for Syntax Errors\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python 3.9\n      uses: actions/setup-python@v2\n      with:\n        python-version: 3.9\n    # - name: Install dependencies\n    #   run: |\n    #    python -m pip install --upgrade pip\n    #    pip install flake8 pytest\n    #    if [ -f requirements.txt ]; then pip install -r requirements.txt; fi\n    # - name: Lint with flake8\n    #  run: |\n        # stop the build if there are Python syntax errors or undefined names\n    #    flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics\n        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide\n    #    flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics\n    # - name: Test with pytest\n    #   run: |\n    #     pytest\n"
  },
  {
    "path": ".gitignore",
    "content": "*.pyc\n*.tar.gz\nbuild/*\ndist/*\npychonet.egg-info/*\n.DS_Store\n__pycache__\npychonet\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    \"python.formatting.provider\": \"black\"\n}"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Scott Phillips\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.ja.md",
    "content": "# ECHONETLite Platform Custom Component for Home Assistant\n\n[![GitHub Release][releases-shield]][releases]\n[![License][license-shield]](LICENSE)\n[![hacs][hacsbadge]][hacs]\n![Project Maintenance][maintenance-shield]\n\nECHONETLite互換機器で使用するためのHomeAssistantカスタムコンポーネント。\nこのカスタムコンポーネントは「pychonet」Python3ライブラリを利用しています。\nまた、そのコンポーネントもこの作者によって維持されています。\n(https://github.com/scottyphillips/pychonet)\n\n**このコンポーネントは、エアコン・ファン・センサー・選択およびスイッチプラットフォームをセットアップします**\n\n# 現在動作確認されている機器:\nフィードバックに基づいて、このカスタムコンポーネントは以下の互換性のあるECHONETLite機器で動作します:\n\n* 三菱電機 MAC-568IF-E WiFi アダプターでコントロールされている以下の機器:\n  * GE シリーズ\n     * MSZ-GE42VAD\n     * MSZ-GE24VAD\n     * MSZ-GL71VGD\n     * MSZ-GL50VGD\n     * MSZ-GL35VGD\n     * MSZ-GL25VGD\n  * AP シリーズ\n     * MSZ-AP22VGD\n     * MSZ-AP25VGD\n     * MSZ-AP50VGD\n  * LN シリーズ\n     * MSZ-LN25VG2\n     * MSZ-LN35VG2\n     * MSZ-LN50VG2\n  * 換気システム\n     * PEA-M100GAA\n     * PEA-RP140GAA\n\n* 三菱電機 HM-W002-AC WiFi アダプターでコントロールされている以下の機器:\n  * JXV シリーズ\n     * MSZ-JXV4018S\n\n* 三菱電機 MAC-578IF2-E WiFi アダプターでコントロールされている以下の機器:\n  * AP シリーズ\n     * MSZ-AP22VGD\n     * MSZ-AP35VGD\n     * MSZ-AP50VGD\n  * 換気システム\n     * PEAD-RP71\n\n* 三菱電機 MAC-600IF WiFi アダプターでコントロールされている以下の機器:\n  * Z シリーズ\n     * MSZ-ZW4022S\n\n* 三菱電機 MAC-900IF WiFi アダプターでコントロールされている以下の機器:\n  * Z シリーズ\n     * MSZ-ZW4024S\n  * XD シリーズ\n     * MSZ-XD2225\n  * R シリーズ\n     * MSZ-BKR2223\n\n* 三菱電機 REF-WLAN001 アダプターでコントロールされている以下の機器:\n  * 冷蔵庫\n     * MR-WZ55H\n\n* 富士通ゼネラル OP-J03DZ WiFi アダプターでコントロールされている以下の機器：\n  * エアコン\n    * 「ノクリア」Cシリーズ\n      * AS-C224R\n      * AS-C254R\n    * 「ノクリア」Vシリーズ\n      * AS-V173N2\n\n* 'MoekadenRoom' ECHONETLite シミュレーター: https://github.com/SonyCSL/MoekadenRoom\n     * エアコンオブジェクト\n     * 照明オブジェクト\n     * 電動ブラインドオブジェクト\n     * 電子錠オブジェクト\n     * 温度計オブジェクト\n     * スマートメーターオブジェクト\n\n* シャープ\n     * エアコン\n         * AY-J22H\n         * AY-L40P\n     * 加湿空気清浄機\n         * KI-HS70\n     * 太陽光発電システム\n         * JH-RWL8\n\n* パナソニック\n     *  エアコン\n         *　CS-221DJ\n     * IP/JEM-A 変換アダプター\n         * HF-JA2-W\n\n* コイズミ照明\n     * スマートブリッジ AE50264E (https://www.koizumi-lt.co.jp/product/jyutaku/tree/ )\n\n* リンナイ\n     * 給湯器 (Wi-Fi機能(ECHONETLite)搭載のリモコン)\n         * スイッチエンティティで、運転・ふろ自動・おいだき・タイマー予約をコントロール可能\n         * タイマー予約を設定するための入力エンティティは、テンプレートと[サービスコール]((Services.ja.md))を使用して設定できます。\n\n* ダイキン\n     * エアコン\n          * ECHONET Lite 対応機器\n\n* オムロン\n    * 太陽光発電用ゲートウェイ\n        * 太陽光発電とグリッド消費を含むホームアシスタントエネルギーダッシュボードの完全サポート。\n        * 各種センサーの負荷状態。\n\n* 低圧スマートメーター(Bルートサービス)\n    * Wi-SUN <-> Ethernet/Wifi ブリッジが必要\n        * [nao-pon/python-echonet-lite](https://github.com/nao-pon/python-echonet-lite)\n        * など\n\n上記の他にも[ECHONET Lite規格](https://echonet.jp/product/echonet-lite/)にリストアップされている多くの機器がコントロールできる可能性があります。\n\n## インストール - ECHONETプロトコルを有効にする\nこのカスタムコンポーネントは、もともと三菱 MAC-568IF-E WiFi アダプター用に設計されました。\nECHONETliteを有効にするための基本ガイドを以下に示します。\n\n公式の Mitsubishi AU/NZ Wifi アプリで「edit unit」設定の「ECHONETlite」プロトコル有効にする必要があります。\n\n※ 日本で発売されている ECHONET Lite 対応のアダプター(HM-W002-AC, HM-W002-ACB など)では設定の必要はないかも知れません。\n\n![echonet][echonetimg]\n\n他の多くの製品はこのカスタムコンポーネントを使用して動作しますが、「ECHONETlite」プロトコルを正しくサポートしている必要があります。 作者は、他のベンダー製品で ECHONET Lite を有効にすることを支援することはできません。\n\n## インストール\n\n### HACS利用\n1. 統合でECHONETLiteを検索します\n2. [インストール]をクリックします\n\n### ダウンロードして配置\n1. 選択したツールを使用して、HA構成（ `configuration.yaml`がある場所）のディレクトリ（フォルダー）を開きます。\n2. そこに`custom_components`ディレクトリ（フォルダ）がない場合は、それを作成する必要があります。\n3.  `custom_components`ディレクトリ（フォルダ）に`echonetlite`という新しいフォルダを作成します。\n4. このリポジトリの`custom_components/echonetlite/`ディレクトリ（フォルダ）から_すべての_ファイルをダウンロードします。\n5. ダウンロードしたファイルを作成した新しいディレクトリ（フォルダ）に配置します。\n\n## コンポーネントの有効化\n1.  ホームアシスタントを再起動し、ブラウザのキャッシュをクリアします\n2. 「構成」->「統合」->「統合の追加」に移動します。\n3. 「echonetlite」統合を選択します。 ホストフィールドにIPアドレスを入力し、プラットフォームに名前を付けます。\n4. プラットフォームは、サポートされているプラットフォームを自動的に構成します。 気候、センサー、スイッチ、ファン、選択。\n5. 構成する追加のデバイスがある場合は、手順2を繰り返します。\n\n## サポートされているエアコンおよび空気清浄機の風量および風向スイングモード設定のオプションの構成\n統合を追加したら、構成->統合に戻ることができます。\nECHONETLiteデバイスの下で[構成]をクリックします。\n必要な風量と風向スイングモードの設定を微調整します。 設定では、統合によりあなたのシステムでサポートされている機能を判別できます。\n注：ECHONETLiteはこれらの設定に対して許可された値を返す手段を提供しないため、適切な特定のオプションを選択することは「試行錯誤」のプロセスです。\n\nオプションを構成して保存するとすぐに、設定が有効になります。\n\n\n## 栄誉殿堂\nスイッチエンティティを作成し、カスタムサービスコールフレームワークを作成してくれた Naoki Sawada (nao-pon) に感謝します。\n\nコードベースとドキュメントの全体的な質の向上してくれた JasonNader に感謝します。\n\n古いコードのリファクタリングとテストを手伝ってくれた khcnz (Karl Chaffey) と gvs に感謝します。\n\nDick Swart、Masaki Tagawa、Paul、khcnz、Kolodnerd、およびAlfie Gernerに、元の \"mitsubishi_hass\" へのコード更新とこのカスタムコンポーネントへの適用に貢献してくれたことに感謝します。\n\nホームアシスタント用に独自のネイティブ Python ECHONET ライブラリを作成するように促してくれた JeffroCarr に感謝します。\n彼自身のリポジトリのいくつかのアイデアは、私自身のコードに実装されました。\n(https://github.com/jethrocarr/echonetlite-hvac-mqtt-service.git)\n\n\"pychonet\"ライブラリの基礎を形成した Node JS の高品質で十分に文書化された ECHONET Lite ライブラリをオープンソーシングしてくれた Futomi Hatano に感謝します。\n(https://github.com/futomi/node-echonet-lite)\n\n\n## ライセンス\n\nこのアプリケーションはMITライセンスの下でライセンスされています。詳細については、ライセンスを参照してください。\n\n***\n[echonetlite_homeassistant]: https://github.com/scottyphillips/echonetlite_homeassistant\n[hacs]: https://github.com/custom-components/hacs\n[hacsbadge]: https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge\n[releases-shield]: https://img.shields.io/github/release/scottyphillips/echonetlite_homeassistant.svg?style=for-the-badge\n[releases]: https://github.com/scottyphillips/echonetlite_homeassistant/releases\n[license-shield]:https://img.shields.io/github/license/scottyphillips/echonetlite_homeassistant?style=for-the-badge\n[buymecoffee]: https://www.buymeacoffee.com/RgKWqyt?style=for-the-badge\n[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge\n[maintenance-shield]: https://img.shields.io/badge/Maintainer-Scott%20Phillips-blue?style=for-the-badge\n[echonetimg]: ECHONET.jpeg\n"
  },
  {
    "path": "README.md",
    "content": "# ECHONETLite Platform Custom Component for Home Assistant\n\n[![GitHub Release][releases-shield]][releases]\n[![License][license-shield]](LICENSE)\n[![hacs][hacsbadge]][hacs]\n![Project Maintenance][maintenance-shield]\n([日本語](https://github.com/scottyphillips/echonetlite_homeassistant/blob/master/README.ja.md))\n\nA Home Assistant custom component for use with ECHONETLite compatible devices.\nThis custom component makes use of the 'pychonet'\nPython3 library also maintained by this author.\n(https://github.com/scottyphillips/pychonet)\n\n*Important note - this repo is no longer in active developement, and i will no longer be actively involved in troubleshooting any issues with the component. However i will respond and approve PRs.\n\n**This component will set up the climate, fan, sensor, select and switch platforms.**\n\n# Current working systems:\nBased upon feedback this custom component works with the following\ncompatible ECHONETLite Devices:\n\n| **Manufacturer**    | **Device**                                     | **ECHONETLite Object Class** | **Home Assistant Entities**      | **Notes**                                                                                         |\n|:--------------------|:-----------------------------------------------|:-----------------------------|:---------------------------------|:--------------------------------------------------------------------------------------------------|\n| Mitsubishi Electric | MAC-568IF-E                                    | HomeAirConditioner           | Climate, Sensor, Select          | WiFi Adaptor connected to various HVAC ducted and split systems. See list below.                  |\n| Mitsubishi Electric | MAC-577IF-E                                    | HomeAirConditioner           | Climate, Sensor, Select          | WiFi Adaptor connected to various HVAC ducted and split systems. See list below.                  |\n| Mitsubishi Electric | MAC-577IF2-E                                   | HomeAirConditioner           | Climate, Sensor, Select          | WiFi Adaptor connected to various HVAC ducted and split systems. See list below.                  |\n| Mitsubishi Electric | MAC-578IF2-E                                   | HomeAirConditioner           | Climate, Sensor, Select          | WiFi Adaptor connected to various HVAC ducted and split systems. See list below.                  |\n| Mitsubishi Electric | MAC-587IF-E                                    | HomeAirConditioner           | Climate, Sensor, Select          | WiFi Adaptor connected to various HVAC ducted and split systems. See list below.                  |\n| Mitsubishi Electric | MAC-588IF-E                                    | HomeAirConditioner           | Climate, Sensor, Select          | WiFi Adaptor connected to various HVAC ducted and split systems. See list below.                  |\n| Mitsubishi Electric | MAC-600IF                                      | HomeAirConditioner           | Climate, Sensor, Select          |                                                                                                   |\n| Mitsubishi Electric | MAC-900IF                                      | HomeAirConditioner           | Climate, Sensor, Select          | WiFi Adaptor connected to various HVAC ducted and split systems. See list below.                  |\n| Mitsubishi Electric | HM-W002-AC                                     | HomeAirConditioner           | Climate, Sensor, Select          | WiFi Adaptor connected to various HVAC ducted and split systems. See list below.                  |\n| Mitsubishi Electric | Eco-Cute SRT-S466A + RMCB-H6SE-T               | ElectricWaterHeater          | Sensor, Select, Switch           |                                                                                                   |\n| Mitsubishi Electric | REF-WLAN001                                    | Refrigerator                 | Sensor                           |                                                                                                   |\n| Sharp               | AY-J22H Air Conditioner                        | HomeAirConditioner           | Climate, Sensor, Select          |                                                                                                   |\n| Sharp               | AY-XP12YHE Air Conditioner                     | HomeAirConditioner           | Climate, Sensor, Select          |                                                                                                   |\n| Sharp               | AY-XP24YHE Air Conditioner                     | HomeAirConditioner           | Climate, Sensor, Select          |                                                                                                   |\n| Sharp               | AY-L40P Air Conditioner                        | HomeAirConditioner           | Climate, Sensor, Select          |                                                                                                   |\n| Sharp               | KI-HS70 Air Purifier                           | HomeAirCleaner               | Fan, Sensor, Select              |                                                                                                   |\n| Sharp               | JH-RWL8 Multi Energy Monitor                   | HomeSolarPower, StorageBattery | Sensor, Select                 |                                                                                                   |\n| Panasonic           | CS-221DJ Air Conditioner                       | HomeAirConditioner           | Climate, Sensor, Select          |                                                                                                   |\n| Panasonic           | CS-362DJ2 Air Conditioner                      | HomeAirConditioner           | Climate, Sensor, Select          |                                                                                                   |\n| Panasonic           | HF-JA2-W                                       |                              | Sensor                           | IP/JEM-A conversion adapter.                                                                      |\n| Panasonic           | Link Plus WTY2001                              | GeneralLighting, Lighting system | Light, Select                | Lighting system is selector of preset scene.                                                      |\n| Panasonic           | Smart Cosmo Type LAN                           | DistributionPanelMeter       | Sensor                           |                                                                                                   |\n| Rinnai              | Hot water systems (ECHONETLite enabled models) |                              | Sensor, Switch, Input            | Input entity to configure Hot Water Timers can be configured by using a template and a [Service Call](Services.md). |\n| Koizumi             | Lighting system AE50264E bridge                | LightingSystem               | Light, Sensor                    | https://www.koizumi-lt.co.jp/product/jyutaku/tree/                                                |\n| Daikin              | ECHONETLite enabled HVAC models.               | HomeAirConditioner           | Climate, Sensor, Select          |                                                                                                   |\n| OMRON               | Home Solar Power Generation                    |                              | Switch, Sensor                   | Full support for Home Assistant Energy Dashboard including solar production and grid consumption. |\n| JDM Electric Meters | Low voltage smart meter (B route service)      |                              | Sensor                           | Require Wi-SUN <-> Ethernet/Wifi bridge. <br> [nao-pon/python-echonet-lite](https://github.com/nao-pon/python-echonet-lite)  |\n| Noritz              | Bathtub and floor heating system               | HotWaterGenerator            | Sensor, Switch                   |                                                                                                   |\n| KDK                 | ECHONETLite enabled Ceiling Fans               | CeilingFan, GeneralLighting  | Fan, Light, Sensor               | Rebranded Panasonic Ceiling Fan.                                                                  |\n| Sony                | 'MoekadenRoom' ECHONETLite Simulator           |                              | Climate, Select, Switch, Sensor  | https://github.com/SonyCSL/MoekadenRoom.                                                          |\n\n* Mitsubishi MAC-568IF-E WiFi Adaptor connected to the following systems:\n  * GE Series\n     * MSZ-GE42VAD\n     * MSZ-GE24VAD\n     * MSZ-GL71VGD\n     * MSZ-GL50VGD\n     * MSZ-GL35VGD\n     * MSZ-GL25VGD\n  * AP Series\n     * MSZ-AP22VGD\n     * MSZ-AP25VGD\n     * MSZ-AP50VGD\n  * LN Series\n     * MSZ-LN25VG2\n     * MSZ-LN35VG2\n     * MSZ-LN50VG2\n  * Ducted\n     * PEA-M100GAA\n     * PEA-M100HAA\n     * PEA-RP140GAA\n  * Bulkhead\n     * SEZ-M71DA\n\n* Mitsubishi MAC-578IF2-E WiFi Adaptor connected to the following systems:\n  * AP Series\n     * MSZ-AP22VGD\n     * MSZ-AP35VGD\n     * MSZ-AP50VGD\n  * Ducted\n     * PEAD-RP71\n\n* Mitsubishi MAC-587IF-E WiFi Adaptor connected to the following systems:\n  * Ducted\n    * PEAD-M50JA2\n\n* Mitsubishi MAC-588IF-E WiFi Adaptor connected to the following systems:\n  * Ducted\n     * PEA-M200LAA\n     * PEAD-M71JAA\n\n* Mitsubishi MAC-600IF WiFi Adaptor connected to the following systems:\n  * Z Series\n     * MSZ-ZW4022S\n\n* Mitsubishi MAC-900IF WiFi Adaptor connected to the following systems:\n  * Z Series\n     * MSZ-ZW4024S\n  * XD Series\n     * MSZ-XD2225\n  * R Series\n     * MSZ-BKR2223\n\n* Mitsubishi HM-W002-AC WiFi Adaptor connected to the following systems:\n  * JXV Series\n     * MSZ-JXV4018S\n\n\n* Mitsubishi REF-WLAN001 WiFi Adaptor connected to the following systems:\n  * Refrigerator\n     * MR-WZ55H\n\n* Fujitsu General OP-J03DZ WiFi Adaptor connected to the following systems:\n  * Air Conditioner\n    * \"Nocria\" C Series\n      * AS-C224R\n      * AS-C254R\n    * \"Nocria\" V Series\n      * AS-V173N2\n\n\n## Installation - Enable ECHONET protocol\nThis Custom Component was originally designed for the Mitsubishi MAC-568IF-E WiFi\nAdaptor, a basic guide for enabling ECHONETlite is provided below.\n\nFrom the official Mitsubishi AU/NZ Wifi App, you will need to enable\nthe 'ECHONET lite' protocol under the 'edit unit' settings.\n\n![echonet][echonetimg]\n\nNote that the proprietary Mitsubishi app (MELCloud/MELView/Kumo Cloud) controls some models in single ˚F or half ˚C, but\nECHONET works in whole ˚C.\n\nMany other products will work using this custom-component, but they must correctly support the 'ECHONET lite' protocol. The author cannot assist with enabling ECHONET Lite for other vendor products.\n\n### Home Network\nIf you have a firewall, ensure port 3610 is unblocked \n\n([EchoNet Specifications](https://echonet.jp/spec_v113_lite_en/))\n\n## Installation\n### Install using HACS\n1. Click the link below or look up 'ECHONETLite Platform' in integrations\\\n   [![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=scottyphillips&repository=echonetlite_homeassistant&category=integration)\n3. Click 'Download', leave the version be and click 'Download' again.\n4. Restart Home Assistant\n\n### Install manually\n1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`).\n2. If you do not have a `custom_components` directory (folder) there, you need to create it.\n3. In the `custom_components` directory (folder) create a new folder called `echonetlite`.\n4. Download _all_ the files from the `custom_components/echonetlite/` directory (folder) in this repository.\n5. Place the files you downloaded in the new directory (folder) you created.\n6. Restart Home Assistant and clear your browser cache\n\n## Setup\n1. In Home Assistant, go to Settings -> Devices & Services -> ADD INTEGRATION.\n2. Select the 'ECHONET Lite' integration. Enter the IP address of the HVAC unit in the host field, and give the platform a name.\n3. Platform will automatically configure any supported platforms e.g. climate, sensor, switch, fan, select.\n4. If you have additional devices to configure then repeat step 1.\n\n## Enabling support for additional Mitsubishi interfaces\n\nSome Mitsubishi WiFi adaptors have hidden support for the ECHONET Lite protocol, which can be enabled by calling the `/smart` endpoint.\n\nFor more information, please see [this issue](https://github.com/scottyphillips/echonetlite_homeassistant/issues/226).\n\nTLDR: Run the following command:\n\n```bash\ncurl -H 'Content-Type: text/xml' -d '<?xml version=\"1.0\" encoding=\"UTF-8\"?><ESV>7WVvmfhMYzGVi70nyFhmKEy9Jo3Hg3994vi9y1kEgDFWd/1ch9RWDUgY4HgsvMHFvP93fQ30AvEJCNcd0GTwPID0F8V5eyMVj/qAQCXFqYrRtJh8MIpm2/h7jZ2SsPj0</ESV>' \"http://${ip}/smart\"\n\n```\n\nReplace `${ip}` with the IP of your adaptor.\n\nIf successful, your should see a response like this:\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><ESV>[encrypted content]</ESV>\n```\n\n## Configuring Options for Fan and swing mode settings for supported HVAC and Air Purifiers.\nOnce you have added the integration, you can go back to configuration -> integrations.\n\nUnder your ECHONETLite device click 'configure'.\n\nFine tune your required fan and swing mode settings. The integration will be able to determine what settings are supported for your system in question.\n\nNOTE: Selecting which specific options are suitable is a 'trial and error' process as ECHONETLite does not provide a means of returning permittted values for these settings.\n\nAs soon as you configure your options and save, the settings will take effect.\n\n## How to find quirks of a manufacturer or specific device\nIt is known that there are manufacturers or specific devices that have data points that are not in the Echonet Lite specification. If the device you are using has these quirks, it will be more useful to find the quirks and define the behavior. Here are the steps to find the quirks as a first step.\n\n1. Enable the debug log on the ECHONETLite integration screen and restart HA.\n2. Once startup is complete, disable the debug log and download the log.\n3. Using the IP address of the target device, check the getmap and setmap data to find values ​​above 240 (0xF0).\nIn the following sample log, 250 (0xFA) and 253 (0xFD) can be get/set. This device is from manufacturer \"Rinnai\" and has eojgc 2 (0x02) and eojcc 130 (0x82)\n```\n{'eojcc': 130, 'eojci': 1, 'eojgc': 2, 'getmap': [128, 224, 129, 241, 130, 131, 147, 243, 244, 245, 246, 247, 136, 248, 249, 138, 250, 251, 252, 157, 253, 158, 254, 159, 255], 'host': '192.168.0.49', 'host_product_code': None, 'manufacturer': 'Rinnai', 'name': None, 'ntfmap': [128, 129, 136, 250, 251, 253], 'setmap': [129, 147, 250, 253], 'uid': '0000590170500000000024cd8d4e84f4', 'uidi': '0000590170500000000024cd8d4e84f4-2-130-1'}]}\n```\n4. Create a quirks file for debugging.\n- File name: quirks/Rinnai/all/0282.py (quirks/{manufacturer}/all/xxyy.py)\n```python\nfrom homeassistant.const import CONF_NAME\n\ndef _hex(edt):\n\treturn edt.hex()\n\nQUIRKS = {\n    0xFA: {\n        \"EPC_FUNCTION\": _hex,\n        \"ENL_OP_CODE\": {\n            CONF_NAME: \"FA\",\n        },\n    },\n    0xFD: {\n        \"EPC_FUNCTION\": _hex,\n        \"ENL_OP_CODE\": {\n            CONF_NAME: \"FD\",\n        },\n    },\n}\n```\n5. When you restart the HA, two new entities, FA and FD, will be configured in this example. Perform zone control and observe whether their values ​​change in a distinctive way.\n6. If a characteristic change occurs, you are lucky! Please submit a new issue, or go a step further and submit your pull request. 👍\n\n\n## Hall of Fame\nThanks Naoki Sawada for creating the switch entity, creating the custom service call framework, and a ton of other improvements.\nMost importantly of all he contributed the translation into 日本語.\n\nThanks to scumbug, lordCONAN, and xen2 for contributing some very interesting devices. \n\nThanks to Jason Nader for all the quality of life updates to the codebase and doco.\n\nThanks to khcnz (Karl Chaffey) and gvs for helping refector the old code\nand contributing to testing.\n\nThanks to Dick Swart, Masaki Tagawa, Paul, khcnz,  Kolodnerd, and Alfie Gerner\nfor each contributing code updates to to the original 'mitsubishi_hass' and therefore this custom component.\n\nThanks to Jeffro Carr who inspired me to write my own native Python ECHONET library for Home Assistant.\nSome ideas in his own repo got implemented in my own code.\n(https://github.com/jethrocarr/echonetlite-hvac-mqtt-service.git)\n\nThanks to Futomi Hatano for open sourcing a high quality and well documented ECHONET Lite library in Node JS that formed the basis of the 'Pychonet' library.\n(https://github.com/futomi/node-echonet-lite)\n\nThanks to all other contributers who I might have missed for raising PRs and issues which has made this little weekend project into something useful for many people. \n\n## License\n\nThis application is licensed under an MIT license, refer to LICENSE for details.\n\n***\n[echonetlite_homeassistant]: https://github.com/scottyphillips/echonetlite_homeassistant\n[hacs]: https://github.com/custom-components/hacs\n[hacsbadge]: https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge\n[releases-shield]: https://img.shields.io/github/release/scottyphillips/echonetlite_homeassistant.svg?style=for-the-badge\n[releases]: https://github.com/scottyphillips/echonetlite_homeassistant/releases\n[license-shield]:https://img.shields.io/github/license/scottyphillips/echonetlite_homeassistant?style=for-the-badge\n[buymecoffee]: https://www.buymeacoffee.com/RgKWqyt?style=for-the-badge\n[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge\n[maintenance-shield]: https://img.shields.io/badge/Maintainer-Scott%20Phillips-blue?style=for-the-badge\n[echonetimg]: ECHONET.jpeg\n"
  },
  {
    "path": "Services.ja.md",
    "content": "# サービスの構成\n\nバージョン 3.5.3 以降、予備的なサポートとして高度なサービス呼び出しが構成されています。 現在、次のデバイスがホームアシスタントサービスをサポートしています。\n\n## ECHONET Lite 対応給湯器 (リンナイ給湯器で動作確認済)\n\nECHONET Lite 対応給湯器は、`echonetlite.set_on_timer_time`サービスコールを使用して自動湯はりタイマーを設定できます。 これは、以下の例のように自動化で使用して、対応する入力エンティティを作成できます。\n\n自動化のサンプル\n\n- センサーエンティティID: sensor.hot_water_set_value_of_on_timer_time\n- 時刻入力エンティティID: input_datetime.hot_water_value_of_on_timer_time\n\n```yaml\nalias: Relay Hot Water On Timer time\ndescription: ''\ntrigger:\n  - platform: state\n    entity_id:\n      - input_datetime.hot_water_value_of_on_timer_time\n    for:\n      hours: 0\n      minutes: 0\n      seconds: 5\n    id: set\n  - platform: state\n    entity_id:\n      - sensor.hot_water_set_value_of_on_timer_time\n    id: get\ncondition:\n  - condition: template\n    value_template: >-\n      {{strptime(states('input_datetime.hot_water_value_of_on_timer_time'), '%H:%M:%S') !=\n      strptime(states('sensor.hot_water_set_value_of_on_timer_time')+':00',\n      '%H:%M:%S')}}\naction:\n  - if:\n      - condition: trigger\n        id: set\n    then:\n      - service: echonetlite.set_on_timer_time\n        entity_id: sensor.hot_water_set_value_of_on_timer_time\n        data_template:\n          timer_time: '{{ states(''input_datetime.hot_water_value_of_on_timer_time'') }}'\n    else:\n      - service: input_datetime.set_datetime\n        entity_id: input_datetime.hot_water_value_of_on_timer_time\n        data_template:\n          time: '{{ states(''sensor.hot_water_set_value_of_on_timer_time'') }}'\nmode: single\n```\n"
  },
  {
    "path": "Services.md",
    "content": "# Configuring Services\n\nPreliminary support for advanced service calls has been configured as of version 3.5.3. At the moment the follow devices support Home Assistant services:\n\n## Hot Water Heater System\n\nECHONET Lite compatible Hot Water Heaters can have their hot water heater timers configured using the `echonetlite.set_on_timer_time` service call. This in turn can be used in automation as per the below example to create a corresponding input entity:\n\n### Automation example\n\n- Sensor entity id as: `sensor.hot_water_set_value_of_on_timer_time`\n- Input entity id as: `input_datetime.hot_water_value_of_on_timer_time`\n\n```yaml\nalias: Relay Hot Water On Timer time\ndescription: ''\ntrigger:\n  - platform: state\n    entity_id:\n      - input_datetime.hot_water_value_of_on_timer_time\n    for:\n      hours: 0\n      minutes: 0\n      seconds: 5\n    id: set\n  - platform: state\n    entity_id:\n      - sensor.hot_water_set_value_of_on_timer_time\n    id: get\ncondition:\n  - condition: template\n    value_template: >-\n      {{strptime(states('input_datetime.hot_water_value_of_on_timer_time'), '%H:%M:%S') !=\n      strptime(states('sensor.hot_water_set_value_of_on_timer_time')+':00',\n      '%H:%M:%S')}}\naction:\n  - if:\n      - condition: trigger\n        id: set\n    then:\n      - service: echonetlite.set_on_timer_time\n        entity_id: sensor.hot_water_set_value_of_on_timer_time\n        data_template:\n          timer_time: '{{ states(''input_datetime.hot_water_value_of_on_timer_time'') }}'\n    else:\n      - service: input_datetime.set_datetime\n        entity_id: input_datetime.hot_water_value_of_on_timer_time\n        data_template:\n          time: '{{ states(''sensor.hot_water_set_value_of_on_timer_time'') }}'\nmode: single\n```\n## Single-byte integer value setting services\n\nWe can create a numeric setting entity with the following automation with the number input helper.\n\nOf course, it can also be used for various automations.\n\nWith future expansion, parameters that can be set numerically on other devices can be added, so I think it can be used conveniently. (Thanks @nao-pon)\n\n  - Sensor entity id as: `sensor.set_value_of_hot_water_temperature`\n  - Input entity id as: `input_number.set_value_of_hot_water_temperature`\n\n### Automation example\n\n```yaml\nalias: Relay Set Value Of Hot Water Temperature\ndescription: ''\ntrigger:\n  - platform: state\n    entity_id:\n      - input_number.set_value_of_hot_water_temperature\n    for:\n      hours: 0\n      minutes: 0\n      seconds: 2\n    id: set\n  - platform: state\n    entity_id:\n      - sensor.set_value_of_hot_water_temperature\n    id: get\ncondition:\n  - condition: template\n    value_template: >-\n      {{int(states('input_number.set_value_of_hot_water_temperature'), 0) !=\n      int(states('sensor.set_value_of_hot_water_temperature'), 0)}}\naction:\n  - if:\n      - condition: trigger\n        id: set\n    then:\n      - service: echonetlite.set_value_int_1b\n        entity_id: sensor.set_value_of_hot_water_temperature\n        data_template:\n          value: '{{ states(''input_number.set_value_of_hot_water_temperature'') }}'\n    else:\n      - service: input_number.set_value\n        entity_id: input_number.set_value_of_hot_water_temperature\n        data_template:\n          value: '{{ states(''sensor.set_value_of_hot_water_temperature'') }}'\nmode: queued\nmax: 2\n```\n"
  },
  {
    "path": "custom_components/echonetlite/__init__.py",
    "content": "\"\"\"The echonetlite integration.\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport logging\nimport os\nimport time as pytime\nfrom datetime import timedelta\nfrom functools import partial\nfrom importlib import import_module\nfrom typing import Any\n\nimport pychonet as echonet\nfrom homeassistant import config_entries\nfrom homeassistant.components.number.const import NumberDeviceClass\nfrom homeassistant.components.sensor.const import SensorDeviceClass\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.const import (\n    CONF_NAME,\n    PERCENTAGE,\n    Platform,\n    UnitOfElectricCurrent,\n    UnitOfElectricPotential,\n    UnitOfEnergy,\n    UnitOfPower,\n    UnitOfTemperature,\n    UnitOfVolume,\n)\nfrom homeassistant.core import HomeAssistant\nfrom homeassistant.exceptions import ConfigEntryNotReady\nfrom homeassistant.util import Throttle\nfrom pychonet import ECHONETAPIClient\nfrom pychonet.echonetapiclient import EchonetMaxOpcError\nfrom pychonet.EchonetInstance import (\n    ENL_CUMULATIVE_POWER,\n    ENL_GETMAP,\n    ENL_INSTANTANEOUS_POWER,\n    ENL_SETMAP,\n    ENL_STATUS,\n    ENL_UID,\n)\nfrom pychonet.HomeAirConditioner import (\n    ENL_AIR_HORZ,\n    ENL_AIR_VERT,\n    ENL_AUTO_DIRECTION,\n    ENL_FANSPEED,\n    ENL_SWING_MODE,\n)\nfrom pychonet.lib.const import ENL_STATMAP, VERSION\nfrom pychonet.lib.epc import EPC_CODE, EPC_SUPER\nfrom pychonet.lib.epc_functions import (\n    DICT_30_ON_OFF,\n    DICT_30_OPEN_CLOSED,\n    DICT_30_TRUE_FALSE,\n    DICT_41_ON_OFF,\n    _hh_mm,\n)\nfrom pychonet.lib.udpserver import UDPServer\n\nfrom .config_flow import ErrorConnect, async_discover_newhost, enumerate_instances\nfrom .const import (\n    CONF_BATCH_SIZE_MAX,\n    CONF_ENABLE_SUPER_ENERGY,\n    DOMAIN,\n    ENABLE_SUPER_ENERGY_DEFAULT,\n    ENL_OP_CODES,\n    ENL_SUPER_CODES,\n    ENL_SUPER_ENERGES,\n    ENL_TIMER_SETTING,\n    MISC_OPTIONS,\n    TEMP_OPTIONS,\n    USER_OPTIONS,\n)\n\n_LOGGER = logging.getLogger(__name__)\nPLATFORMS = [\n    Platform.BINARY_SENSOR,\n    Platform.SENSOR,\n    Platform.CLIMATE,\n    Platform.SELECT,\n    Platform.LIGHT,\n    Platform.FAN,\n    Platform.SWITCH,\n    Platform.TIME,\n    Platform.NUMBER,\n    Platform.COVER,\n]\nPARALLEL_UPDATES = 0\nMIN_TIME_BETWEEN_UPDATES = timedelta(seconds=1)\nMAX_UPDATE_BATCH_SIZE = 10\nMIN_UPDATE_BATCH_SIZE = 3\nSETUP_BUDGET = 45.0\nDISCOVERY_MAX_BUDGET = 20.0\nDISCOVERY_MIN_BUDGET = 8.0\nINSTANCE_MAX_BUDGET = 8.0\nINSTANCE_MIN_BUDGET = 4.0\nINSTANCE_RETRY_DELAY = 0.3\n\n\ndef _remaining_setup_budget(started: float) -> float:\n    \"\"\"Return remaining setup budget in seconds.\"\"\"\n    return SETUP_BUDGET - (pytime.monotonic() - started)\n\n\nasync def _run_with_timeout(coro, timeout_s: float):\n    \"\"\"Run coroutine with timeout.\"\"\"\n    async with asyncio.timeout(timeout_s):\n        return await coro\n\n\ndef get_device_name(connector, config) -> str:\n    if connector._name:\n        return connector._name\n    if connector._instance._eojci > 1:\n        return f\"{config.title} {connector._instance._eojci}\"\n    return config.title\n\n\ndef get_name_by_epc_code(\n    jgc: int,\n    jcc: int,\n    code: int,\n    unknown: str | None = None,\n    given_name: str | None = None,\n) -> str:\n    if given_name != None:\n        return given_name\n    if code in EPC_SUPER:\n        return EPC_SUPER[code]\n    else:\n        if unknown == None:\n            unknown = f\"({code})\"\n        name = EPC_CODE.get(jgc, {}).get(jcc, {}).get(code, None)\n        if name == None:\n            _code = f\"[{hex(jgc)}({jgc})-{hex(jcc)}({jcc})-{hex(code)}({code})]\"\n            _LOGGER.warning(\n                f\"{_code} - Unable to resolve the item name. \"\n                + f\"Please report the unknown code {_code} at the issue tracker on GitHub!\"\n            )\n            if unknown == None:\n                name = f\"({code})\"\n            else:\n                name = unknown\n        return name\n\n\ndef polling_update_debug_log(values: dict[int, Any], conn_instance: ECHONETConnector):\n    eojgc = conn_instance._eojgc\n    eojcc = conn_instance._eojcc\n    debug_log = \"\\nECHONETlite polling update data:\\n\"\n    for value in list(values.keys()):\n        name = conn_instance._enl_op_codes.get(value, {}).get(CONF_NAME)\n        debug_log = (\n            debug_log\n            + f\" - {get_name_by_epc_code(eojgc, eojcc, value, None, name)} {value:#x}({value}): {values[value]}\\n\"\n        )\n    return debug_log\n\n\ndef get_unit_by_devise_class(device_class: str) -> str | None:\n    if (\n        device_class == SensorDeviceClass.TEMPERATURE\n        or device_class == NumberDeviceClass.TEMPERATURE\n    ):\n        unit = UnitOfTemperature.CELSIUS\n    elif (\n        device_class == SensorDeviceClass.ENERGY\n        or device_class == NumberDeviceClass.ENERGY\n    ):\n        unit = UnitOfEnergy.WATT_HOUR\n    elif (\n        device_class == SensorDeviceClass.POWER\n        or device_class == NumberDeviceClass.POWER\n    ):\n        unit = UnitOfPower.WATT\n    elif (\n        device_class == SensorDeviceClass.CURRENT\n        or device_class == NumberDeviceClass.CURRENT\n    ):\n        unit = UnitOfElectricCurrent.AMPERE\n    elif (\n        device_class == SensorDeviceClass.VOLTAGE\n        or device_class == NumberDeviceClass.VOLTAGE\n    ):\n        unit = UnitOfElectricPotential.VOLT\n    elif (\n        device_class == SensorDeviceClass.HUMIDITY\n        or device_class == SensorDeviceClass.BATTERY\n        or device_class == NumberDeviceClass.HUMIDITY\n        or device_class == NumberDeviceClass.BATTERY\n    ):\n        unit = PERCENTAGE\n    elif device_class == SensorDeviceClass.GAS or device_class == NumberDeviceClass.GAS:\n        unit = UnitOfVolume.CUBIC_METERS\n    elif (\n        device_class == SensorDeviceClass.WATER\n        or device_class == NumberDeviceClass.WATER\n    ):\n        unit = UnitOfVolume.CUBIC_METERS\n    else:\n        unit = None\n\n    return unit\n\n\ndef regist_as_inputs(epc_function_data):\n    if epc_function_data:\n        if type(epc_function_data) == list:\n            if type(epc_function_data[1]) == dict and len(epc_function_data[1]) > 1:\n                return True  # Switch or Select\n            if callable(epc_function_data[0]) and epc_function_data[0] == _hh_mm:\n                return True  # Time\n        elif callable(epc_function_data) and epc_function_data == _hh_mm:\n            return True  # Time\n    return False\n\n\ndef regist_as_binary_sensor(epc_function_data):\n    if epc_function_data:\n        if type(epc_function_data) == list:\n            if epc_function_data[1] in (\n                DICT_41_ON_OFF,\n                DICT_30_TRUE_FALSE,\n                DICT_30_ON_OFF,\n                DICT_30_OPEN_CLOSED,\n            ):\n                return True\n    return False\n\n\nasync def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:\n    entry.async_on_unload(entry.add_update_listener(update_listener))\n    started = pytime.monotonic()\n    host = None\n    udp = None\n    server = None\n\n    async def discover_callback(host):\n        await async_discover_newhost(hass, host)\n\n    def unload_config_entry():\n        if server != None:\n            _LOGGER.debug(\n                f\"Called unload_config_entry() try to remove {host} from server._state.\"\n            )\n            if server._state.get(host):\n                server._state.pop(host)\n            # Remove update callback function\n            for _key in list(server._update_callbacks.keys()):\n                if _key.startswith(host):\n                    del server._update_callbacks[_key]\n\n    entry.async_on_unload(unload_config_entry)\n\n    if DOMAIN in hass.data:  # maybe set up by config entry?\n        _LOGGER.debug(\"ECHONETlite platform is already started.\")\n        server = hass.data[DOMAIN][\"api\"]\n        hass.data[DOMAIN].update({entry.entry_id: []})\n    else:  # setup API\n        _LOGGER.debug(\"Starting up ECHONETlite platform..\")\n        _LOGGER.debug(f\"pychonet version is {VERSION}\")\n        hass.data.setdefault(DOMAIN, {})\n        hass.data[DOMAIN].update({entry.entry_id: []})\n        udp = UDPServer()\n        udp.run(\"0.0.0.0\", 3610, loop=hass.loop)\n        server = ECHONETAPIClient(udp)\n        server._debug_flag = True\n        server._logger = _LOGGER.debug\n        server._message_timeout = 300\n        server._discover_callback = discover_callback\n        hass.data[DOMAIN].update({\"api\": server})\n\n    if not entry.pref_disable_new_entities:\n        host = (\n            entry.data[\"host\"]\n            if \"host\" in entry.data\n            else entry.data[\"instances\"][0][\"host\"]\n        )\n\n        # make sure multicast is registered with the local IP used to reach this host\n        server._server.register_multicast_from_host(host)\n\n        try:\n            remaining = _remaining_setup_budget(started)\n            if remaining < DISCOVERY_MIN_BUDGET:\n                raise ConfigEntryNotReady(\n                    f\"Not enough setup time left for ECHONET Lite discovery on {host}\"\n                )\n\n            discovery_budget = min(remaining, DISCOVERY_MAX_BUDGET)\n\n            instances = await _run_with_timeout(\n                enumerate_instances(hass, host),\n                discovery_budget,\n            )\n\n        except ErrorConnect as ex:\n            raise ConfigEntryNotReady(\n                f\"Connection error while connecting to {host}: {ex}\"\n            ) from ex\n        except (TimeoutError, asyncio.TimeoutError) as ex:\n            raise ConfigEntryNotReady(\n                f\"ECHONET Lite discovery timed out for {host}\"\n            ) from ex\n        except asyncio.CancelledError:\n            raise\n\n        # Maintain old entity configuration types to avoid duplicate creation of new entities\n        _registed_instances = {}\n        for _instance in entry.data[\"instances\"]:\n            _uidi = f\"{_instance['uid']}-{_instance['eojgc']}-{_instance['eojcc']}-{_instance['eojci']}\"\n            _registed_instances[_uidi] = _instance\n        for _instance in instances:\n            _uidi = _instance[\"uidi\"]\n            _registed = _registed_instances.get(_uidi)\n            if _registed and _registed.get(\"uidi\") == None:\n                # keep old type config (echonetlite < 3.6.0)\n                del _instance[\"uidi\"]\n\n        hass.config_entries.async_update_entry(\n            entry, title=entry.title, data={\"host\": host, \"instances\": instances}\n        )\n\n    for instance in entry.data[\"instances\"]:\n        # auto update to new style\n        if \"ntfmap\" not in instance:\n            instance[\"ntfmap\"] = []\n        echonetlite = None\n        host = instance[\"host\"]\n        eojgc = instance[\"eojgc\"]\n        eojcc = instance[\"eojcc\"]\n        eojci = instance[\"eojci\"]\n        ntfmap = instance[\"ntfmap\"]\n        getmap = instance[\"getmap\"]\n        setmap = instance[\"setmap\"]\n        uid = instance[\"uid\"]\n\n        # manually update API states using config entry data\n        if host not in list(server._state):\n            server._state[host] = {\n                \"instances\": {\n                    eojgc: {\n                        eojcc: {\n                            eojci: {\n                                ENL_STATMAP: ntfmap,\n                                ENL_SETMAP: setmap,\n                                ENL_GETMAP: getmap,\n                                ENL_UID: uid,\n                            }\n                        }\n                    }\n                }\n            }\n        if eojgc not in list(server._state[host][\"instances\"]):\n            server._state[host][\"instances\"].update(\n                {\n                    eojgc: {\n                        eojcc: {\n                            eojci: {\n                                ENL_STATMAP: ntfmap,\n                                ENL_SETMAP: setmap,\n                                ENL_GETMAP: getmap,\n                                ENL_UID: uid,\n                            }\n                        }\n                    }\n                }\n            )\n        if eojcc not in list(server._state[host][\"instances\"][eojgc]):\n            server._state[host][\"instances\"][eojgc].update(\n                {\n                    eojcc: {\n                        eojci: {\n                            ENL_STATMAP: ntfmap,\n                            ENL_SETMAP: setmap,\n                            ENL_GETMAP: getmap,\n                            ENL_UID: uid,\n                        }\n                    }\n                }\n            )\n        if eojci not in list(server._state[host][\"instances\"][eojgc][eojcc]):\n            server._state[host][\"instances\"][eojgc][eojcc].update(\n                {\n                    eojci: {\n                        ENL_STATMAP: ntfmap,\n                        ENL_SETMAP: setmap,\n                        ENL_GETMAP: getmap,\n                        ENL_UID: uid,\n                    }\n                }\n            )\n\n        echonetlite = ECHONETConnector(instance, hass, entry)\n        await echonetlite.startup()\n        try:\n            # Since there is a small chance of failure, perform a few retries for each instance.\n            for retry in range(1, 4):\n                remaining = _remaining_setup_budget(started)\n                if remaining < INSTANCE_MIN_BUDGET:\n                    raise ConfigEntryNotReady(\n                        f\"Not enough setup time left to initialize ECHONET Lite instances for {host}\"\n                    )\n\n                per_try_budget = min(remaining, INSTANCE_MAX_BUDGET)\n\n                try:\n                    await _run_with_timeout(\n                        echonetlite.async_update(),\n                        per_try_budget,\n                    )\n                    hass.data[DOMAIN][entry.entry_id].append(\n                        {\"instance\": instance, \"echonetlite\": echonetlite}\n                    )\n                    break\n\n                except (TimeoutError, asyncio.TimeoutError) as ex:\n                    _LOGGER.debug(\n                        \"Setting up ECHONET instance %s-%s-%s on %s timed out \"\n                        \"(retry %s/3, remaining %.1fs)\",\n                        eojgc,\n                        eojcc,\n                        eojci,\n                        host,\n                        retry,\n                        _remaining_setup_budget(started),\n                    )\n                    if retry == 3:\n                        raise ConfigEntryNotReady(\n                            f\"Initial update timed out for {host}\"\n                        ) from ex\n\n                    await asyncio.sleep(INSTANCE_RETRY_DELAY)\n\n        except asyncio.CancelledError:\n            raise\n        except KeyError as ex:\n            raise ConfigEntryNotReady(\n                f\"IP address change was detected during setup of {host}\"\n            ) from ex\n\n    _LOGGER.debug(f\"Plaform entry data - {entry.data}\")\n\n    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)\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    if unload_ok:\n        hass.data[DOMAIN].pop(entry.entry_id)\n\n    return unload_ok\n\n\n# TODO update for Air Cleaner\nasync def update_listener(hass, entry):\n    for instance in hass.data[DOMAIN][entry.entry_id]:\n        if instance[\"instance\"][\"eojgc\"] == 1 and instance[\"instance\"][\"eojcc\"] == 48:\n            for option in USER_OPTIONS.keys():\n                if (\n                    entry.options.get(USER_OPTIONS[option][\"option\"]) is not None\n                ):  # check if options has been created\n                    if isinstance(\n                        entry.options.get(USER_OPTIONS[option][\"option\"]), list\n                    ):\n                        if (\n                            len(entry.options.get(USER_OPTIONS[option][\"option\"])) > 0\n                        ):  # if it has been created then check list length.\n                            instance[\"echonetlite\"]._user_options.update(\n                                {\n                                    option: entry.options.get(\n                                        USER_OPTIONS[option][\"option\"]\n                                    )\n                                }\n                            )\n                        else:\n                            instance[\"echonetlite\"]._user_options.update(\n                                {option: False}\n                            )\n                    else:\n                        instance[\"echonetlite\"]._user_options.update(\n                            {option: entry.options.get(USER_OPTIONS[option][\"option\"])}\n                        )\n            for option in TEMP_OPTIONS.keys():\n                if entry.options.get(option) is not None:\n                    instance[\"echonetlite\"]._user_options.update(\n                        {option: entry.options.get(option)}\n                    )\n\n        for key, option in MISC_OPTIONS.items():\n            if entry.options.get(key) is not None or option.get(\"default\"):\n                instance[\"echonetlite\"]._user_options.update(\n                    {key: entry.options.get(key, option.get(\"default\"))}\n                )\n\n        _need_reload = False\n        for func in instance[\"echonetlite\"]._update_option_func:\n            _need_reload |= bool(func())\n\n        if _need_reload:\n            return await hass.config_entries.async_reload(entry.entry_id)\n        else:\n            return None\n\n\nasync def get_echonet_connector():\n    return\n\n\nclass ECHONETConnector:\n    \"\"\"EchonetAPIConnector is used to centralise API calls for  Echonet devices.\n    API calls are aggregated per instance (not per node!)\"\"\"\n\n    def __init__(self, instance, hass, entry):\n        self.hass = hass\n        self._host = instance[\"host\"]\n        self._eojgc = instance[\"eojgc\"]\n        self._eojcc = instance[\"eojcc\"]\n        self._eojci = instance[\"eojci\"]\n        self._update_flag_batches = []\n        self._update_data = {}\n        self._api = hass.data[DOMAIN][\"api\"]\n        self._update_callbacks = []\n        self._update_option_func = []\n        self._update_flags_full_list = []\n        self._ntfPropertyMap = instance[\"ntfmap\"]\n        self._getPropertyMap = instance[\"getmap\"]\n        self._setPropertyMap = instance[\"setmap\"]\n        self._manufacturer = None\n        self._host_product_code = None\n        self._enl_op_codes = ENL_OP_CODES.get(self._eojgc, {}).get(self._eojcc, {})\n        if \"manufacturer\" in instance:\n            self._manufacturer = instance[\"manufacturer\"]\n        if \"host_product_code\" in instance:\n            self._host_product_code = instance[\"host_product_code\"]\n        self._uid = instance.get(\"uid\")\n        self._uidi = instance.get(\"uidi\")\n        self._name = instance.get(\"name\")\n        self._api.register_async_update_callbacks(\n            self._host,\n            self._eojgc,\n            self._eojcc,\n            self._eojci,\n            self.async_update_callback,\n        )\n        self._entry = entry\n\n        self._instance = echonet.Factory(\n            self._host, self._api, self._eojgc, self._eojcc, self._eojci\n        )\n\n    async def startup(self):\n        entry = self._entry\n\n        _LOGGER.debug(\n            f\"Starting ECHONETLite {self._instance.__class__.__name__} instance for {self._eojgc}-{self._eojcc}-{self._eojci}, manufacturer: {self._manufacturer}, host_product_code: {self._host_product_code} at {self._host}\"\n        )\n\n        # Check Check the definition of quirk\n        await self._load_quirk()\n\n        # TODO this looks messy.\n        self._user_options = {\n            ENL_FANSPEED: False,\n            ENL_AUTO_DIRECTION: False,\n            ENL_SWING_MODE: False,\n            ENL_AIR_VERT: False,\n            ENL_AIR_HORZ: False,\n            \"min_temp_heat\": 15,\n            \"max_temp_heat\": 35,\n            \"min_temp_cool\": 15,\n            \"max_temp_cool\": 35,\n            \"min_temp_auto\": 15,\n            \"max_temp_auto\": 35,\n        }\n        # User selectable options for fan + swing modes for HVAC\n        for option in USER_OPTIONS.keys():\n            if (\n                entry.options.get(USER_OPTIONS[option][\"option\"]) is not None\n            ):  # check if options has been created\n                if (\n                    len(entry.options.get(USER_OPTIONS[option][\"option\"])) > 0\n                ):  # if it has been created then check list length.\n                    self._user_options[option] = entry.options.get(\n                        USER_OPTIONS[option][\"option\"]\n                    )\n\n        # Temperature range options for heat, cool and auto modes\n        for option in TEMP_OPTIONS.keys():\n            if entry.options.get(option) is not None:\n                self._user_options[option] = entry.options.get(option)\n\n        # Misc options\n        for key, option in MISC_OPTIONS.items():\n            if entry.options.get(key) is not None:\n                self._user_options[key] = entry.options.get(key, option.get(\"default\"))\n\n        # Make _update_flags_full_list\n        self._make_update_flags_full_list()\n        self._update_option_func.append(self._make_update_flags_full_list)\n\n        # Make batch request flags\n        self._make_batch_request_flags()\n        self._update_option_func.append(self._make_batch_request_flags)\n\n        _LOGGER.debug(f\"UID for ECHONETLite instance at {self._host} is {self._uid}.\")\n        if self._uid is None:\n            self._uid = f\"{self._host}-{self._eojgc}-{self._eojcc}-{self._eojci}\"\n\n    @Throttle(MIN_TIME_BETWEEN_UPDATES)\n    async def async_update(self, **kwargs):\n        try:\n            await self.async_update_data(kwargs)\n        except EchonetMaxOpcError as ex:\n            # Adjust the maximum number of properties for batch requests\n            batch_size_max = self._user_options.get(\n                CONF_BATCH_SIZE_MAX, MAX_UPDATE_BATCH_SIZE\n            )\n            batch_data_len = max(\n                ex.args[0],\n                MIN_UPDATE_BATCH_SIZE,\n                batch_size_max - 1,\n            )\n            if batch_data_len >= batch_size_max:\n                _LOGGER.error(\n                    f\"The integration has adjusted the number of batch requests to {self._host} to {batch_size_max}, but no response is received. Please check and try restarting the device etc.\"\n                )\n                return None\n            self._user_options[CONF_BATCH_SIZE_MAX] = batch_data_len\n            entry_options = dict(self._entry.options)\n            entry_options.update({CONF_BATCH_SIZE_MAX: batch_data_len})\n            self.hass.config_entries.async_update_entry(\n                self._entry, options=entry_options\n            )\n            self._make_batch_request_flags()\n\n            await self.async_update(**kwargs)\n\n    async def async_update_data(self, kwargs):\n        update_data = {}\n        no_request = \"no_request\" in kwargs and kwargs[\"no_request\"]\n        for i, flags in enumerate(self._update_flag_batches):\n            if i > 0:\n                # Interval 100ms to next request\n                await asyncio.sleep(0.1)\n            batch_data = await self._instance.update(flags, no_request)\n            if batch_data is not False:\n                if len(flags) == 1:\n                    update_data[flags[0]] = batch_data\n                elif isinstance(batch_data, dict):\n                    update_data.update(batch_data)\n        _LOGGER.debug(polling_update_debug_log(update_data, self))\n        if len(update_data) > 0:\n            self._update_data.update(update_data)\n\n    async def async_update_callback(self, isPush: bool = False):\n        await self.async_update_data(kwargs={\"no_request\": True})\n        for update_func in self._update_callbacks:\n            await update_func(isPush)\n\n    def _make_update_flags_full_list(self):\n        _prev_update_flags_full_list = self._update_flags_full_list.copy()\n        # Make EPC codes for update\n        self._update_flags_full_list = []\n        # Is enabled CONF_ENABLE_SUPER_ENERGY\n        _enabled_super_energy = self._user_options.get(\n            CONF_ENABLE_SUPER_ENERGY,\n            ENABLE_SUPER_ENERGY_DEFAULT.get(self._eojgc, {}).get(self._eojcc, True),\n        )\n        # General purpose data items\n        flags = [] # PR 246\n        if _enabled_super_energy:\n            _enl_super_codes = ENL_SUPER_CODES\n        else:\n            _enl_super_codes = {\n                k: v for k, v in ENL_SUPER_CODES.items() if k not in ENL_SUPER_ENERGES\n            }\n        flags += list(_enl_super_codes)\n\n        # Get supported EPC_FUNCTIONS in pychonet object class\n        _epc_keys = set(self._instance.EPC_FUNCTIONS.keys()) - set(EPC_SUPER.keys())\n        for item in self._getPropertyMap:\n            if item in _epc_keys:\n                flags.append(item)\n\n        for value in flags:\n            if value in self._getPropertyMap:\n                self._update_flags_full_list.append(value)\n                self._update_data[value] = None\n\n        _LOGGER.debug(\n            f\"Echonet device {self._host}-{self._eojgc}-{self._eojcc}-{self._eojci} update_flags_full_list: {self._update_flags_full_list}\"\n        )\n\n        return _prev_update_flags_full_list != self._update_flags_full_list\n\n    def _make_batch_request_flags(self):\n        # Split list of codes into batches of 10\n        self._update_flag_batches = []\n        start_index = 0\n        full_list_length = len(self._update_flags_full_list)\n\n        # Make batch request flags\n        batch_size_max = self._user_options.get(\n            CONF_BATCH_SIZE_MAX, MAX_UPDATE_BATCH_SIZE\n        )\n        while start_index + batch_size_max < full_list_length:\n            self._update_flag_batches.append(\n                self._update_flags_full_list[start_index : start_index + batch_size_max]\n            )\n            start_index += batch_size_max\n        self._update_flag_batches.append(\n            self._update_flags_full_list[start_index:full_list_length]\n        )\n        _LOGGER.debug(\n            f\"Echonet device {self._host}-{self._eojgc}-{self._eojcc}-{self._eojci} batch request flags list: {self._update_flag_batches}\"\n        )\n\n    def register_async_update_callbacks(self, update_func):\n        self._update_callbacks.append(update_func)\n\n    def add_update_option_listener(self, update_func):\n        self._update_option_func.append(update_func)\n\n    async def _load_quirk(self):\n        # self._manufacturer, self._host_product_code, self._eojgc, self._eojcc\n        def update(extention):\n            for epc in extention.QUIRKS:\n                if func := extention.QUIRKS[epc].get(\"EPC_FUNCTION\"):\n                    self._instance.EPC_FUNCTIONS.update({epc: func})\n                    if op_code := extention.QUIRKS[epc].get(\"ENL_OP_CODE\"):\n                        self._enl_op_codes.update({epc: op_code})\n            _LOGGER.debug(f\"Echonet EPC_FUNCTIONS is: {self._instance.EPC_FUNCTIONS}\")\n            _LOGGER.debug(f\"Echonet _enl_op_codes is: {self._enl_op_codes}\")\n\n        if self._manufacturer:\n            check = [\n                \"quirks\",\n                self._manufacturer,\n                \"all\",\n                \"{:0>2X}\".format(self._eojgc) + \"{:0>2X}\".format(self._eojcc),\n            ]\n            path = os.path.dirname(__file__) + \"/\" + \"/\".join(check) + \".py\"\n            _LOGGER.debug(f\"Echonet _load_quirk check path is: {path}\")\n            if os.path.isfile(path):\n                mod = \".\" + \".\".join(check)\n                _LOGGER.debug(f\"Echonet import module is: {mod} of {__package__}\")\n                extention = await self.hass.async_add_executor_job(\n                    partial(import_module, mod, package=__package__)\n                )\n                update(extention)\n            if self._host_product_code:\n                check = [\n                    \"quirks\",\n                    self._manufacturer,\n                    self._host_product_code,\n                    \"{:0>2X}\".format(self._eojgc) + \"{:0>2X}\".format(self._eojcc),\n                ]\n                path = os.path.dirname(__file__) + \"/\" + \"/\".join(check) + \".py\"\n                _LOGGER.debug(f\"Echonet _load_quirk check path is: {path}\")\n                if os.path.isfile(path):\n                    mod = \".\" + \".\".join(check)\n                    _LOGGER.debug(f\"Echonet import module is: {mod} of {__package__}\")\n                    extention = await self.hass.async_add_executor_job(\n                        partial(import_module, mod, package=__package__)\n                    )\n                    update(extention)\n"
  },
  {
    "path": "custom_components/echonetlite/binary_sensor.py",
    "content": "\"\"\"Support for ECHONETLite sensors.\"\"\"\n\nimport logging\nimport voluptuous as vol\n\nfrom homeassistant.const import (\n    CONF_ICON,\n    CONF_NAME,\n    CONF_SERVICE,\n    CONF_TYPE,\n    CONF_UNIT_OF_MEASUREMENT,\n)\nfrom homeassistant.helpers import config_validation as cv, entity_platform\nfrom homeassistant.components.binary_sensor import BinarySensorEntity\nfrom homeassistant.components.sensor import SensorDeviceClass\nfrom homeassistant.components.binary_sensor import BinarySensorDeviceClass\nfrom homeassistant.exceptions import InvalidStateError, NoEntitySpecifiedError\n\nfrom pychonet.lib.eojx import EOJX_CLASS\nfrom pychonet.lib.epc_functions import (\n    DATA_STATE_OFF,\n    DATA_STATE_ON,\n    DATA_STATE_CLOSE,\n    DATA_STATE_OPEN,\n    EPC_SUPER_FUNCTIONS,\n)\n\nfrom . import (\n    get_name_by_epc_code,\n    get_unit_by_devise_class,\n    get_device_name,\n    regist_as_inputs,\n    regist_as_binary_sensor,\n)\nfrom .const import (\n    DOMAIN,\n    ENABLE_SUPER_ENERGY_DEFAULT,\n    ENL_OP_CODES,\n    CONF_STATE_CLASS,\n    ENL_SUPER_CODES,\n    ENL_SUPER_ENERGES,\n    NON_SETUP_SINGLE_ENYITY,\n    TYPE_SWITCH,\n    TYPE_SELECT,\n    TYPE_TIME,\n    TYPE_NUMBER,\n    SERVICE_SET_ON_TIMER_TIME,\n    SERVICE_SET_INT_1B,\n    CONF_FORCE_POLLING,\n    CONF_ENABLE_SUPER_ENERGY,\n    TYPE_DATA_DICT,\n    TYPE_DATA_ARRAY_WITH_SIZE_OPCODE,\n    CONF_DISABLED_DEFAULT,\n    CONF_MULTIPLIER,\n    CONF_MULTIPLIER_OPCODE,\n    CONF_MULTIPLIER_OPTIONAL_OPCODE,\n    CONF_ICON_POSITIVE,\n    CONF_ICON_NEGATIVE,\n    CONF_ICON_ZERO,\n)\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_setup_entry(hass, config, async_add_entities, discovery_info=None):\n    entities = []\n    platform = entity_platform.async_get_current_platform()\n    for entity in hass.data[DOMAIN][config.entry_id]:\n        _LOGGER.debug(f\"Configuring ECHONETLite sensor {entity}\")\n        _LOGGER.debug(\n            f\"Update flags for this sensor are {entity['echonetlite']._update_flags_full_list}\"\n        )\n        eojgc = entity[\"instance\"][\"eojgc\"]\n        eojcc = entity[\"instance\"][\"eojcc\"]\n\n        if entity[\"echonetlite\"]._user_options.get(\n            CONF_ENABLE_SUPER_ENERGY,\n            ENABLE_SUPER_ENERGY_DEFAULT.get(eojgc, {}).get(eojcc, True),\n        ):\n            _enl_super_codes = ENL_SUPER_CODES\n        else:\n            _enl_super_codes = {\n                k: v for k, v in ENL_SUPER_CODES.items() if not k in ENL_SUPER_ENERGES\n            }\n        _enl_op_codes = entity[\"echonetlite\"]._enl_op_codes | _enl_super_codes\n        _epc_functions = (\n            entity[\"echonetlite\"]._instance.EPC_FUNCTIONS | EPC_SUPER_FUNCTIONS\n        )\n        # For all other devices, sensors will be configured but customise if applicable.\n        for op_code in list(\n            set(entity[\"echonetlite\"]._update_flags_full_list)\n            - NON_SETUP_SINGLE_ENYITY.get(eojgc, {}).get(eojcc, set())\n        ):\n            # Check DeviceClass and regist_as_binary_sensor()\n            if not isinstance(\n                _enl_op_codes.get(op_code, {}).get(CONF_TYPE), BinarySensorDeviceClass\n            ) and not regist_as_binary_sensor(_epc_functions.get(op_code, None)):\n                continue\n\n            # Is settable\n            _is_settable = op_code in entity[\"instance\"][\"setmap\"]\n            # Conf Keys list\n            _keys = _enl_op_codes.get(op_code, {}).keys()\n            # For backward compatibility (Deprecated)\n            _has_conf_service = CONF_SERVICE in _keys\n            # Check this op_code will be configured as input(switch, select ot time) entity\n            if (\n                _is_settable\n                and not _has_conf_service\n                and regist_as_inputs(_epc_functions.get(op_code, None))\n            ):\n                continue\n            # Configuration check with ENL_OP_CODE definition\n            if len(_keys):\n                if (\n                    _is_settable\n                    and not _has_conf_service\n                    and (\n                        TYPE_SWITCH in _keys\n                        or TYPE_SELECT in _keys\n                        or TYPE_TIME in _keys\n                        or TYPE_NUMBER in _keys\n                    )\n                ):\n                    continue  # dont configure as sensor, it will be configured as switch, select or time instead.\n\n                # For backward compatibility (Deprecated)\n                if (\n                    _is_settable and _has_conf_service\n                ):  # Some devices support advanced service calls.\n                    _enl_op_codes[op_code][CONF_DISABLED_DEFAULT] = True\n                    for service_name in _enl_op_codes.get(op_code, {}).get(\n                        CONF_SERVICE\n                    ):\n                        if service_name == SERVICE_SET_ON_TIMER_TIME:\n                            platform.async_register_entity_service(\n                                service_name,\n                                {vol.Required(\"timer_time\"): cv.time_period},\n                                \"async_\" + service_name,\n                            )\n                        elif service_name == SERVICE_SET_INT_1B:\n                            platform.async_register_entity_service(\n                                service_name,\n                                {\n                                    vol.Required(\"value\"): cv.positive_int,\n                                    vol.Optional(\n                                        \"epc\", default=op_code\n                                    ): cv.positive_int,\n                                },\n                                \"async_\" + service_name,\n                            )\n\n                if TYPE_DATA_DICT in _keys:\n                    type_data = _enl_op_codes.get(op_code, {}).get(TYPE_DATA_DICT)\n                    if isinstance(type_data, list):\n                        for attr_key in type_data:\n                            entities.append(\n                                EchonetBinarySensor(\n                                    entity[\"echonetlite\"],\n                                    config,\n                                    op_code,\n                                    _enl_op_codes.get(op_code) | {\"dict_key\": attr_key},\n                                    hass,\n                                )\n                            )\n                        continue\n                    else:\n                        continue\n                if TYPE_DATA_ARRAY_WITH_SIZE_OPCODE in _keys:\n                    array_size_op_code = _enl_op_codes[op_code][\n                        TYPE_DATA_ARRAY_WITH_SIZE_OPCODE\n                    ]\n                    array_max_size = await entity[\"echonetlite\"]._instance.update(\n                        array_size_op_code\n                    )\n                    for x in range(0, array_max_size):\n                        attr = _enl_op_codes[op_code].copy()\n                        attr[\"accessor_index\"] = x\n                        attr[\"accessor_lambda\"] = lambda value, index: (\n                            value[\"values\"][index] if index < value[\"range\"] else None\n                        )\n                        entities.append(\n                            EchonetBinarySensor(\n                                entity[\"echonetlite\"],\n                                config,\n                                op_code,\n                                attr,\n                            )\n                        )\n                    continue\n                else:\n                    entities.append(\n                        EchonetBinarySensor(\n                            entity[\"echonetlite\"],\n                            config,\n                            op_code,\n                            _enl_op_codes.get(\n                                op_code,\n                                ENL_OP_CODES[\"default\"] | {CONF_DISABLED_DEFAULT: True},\n                            ),\n                            hass,\n                        )\n                    )\n                continue\n            entities.append(\n                EchonetBinarySensor(\n                    entity[\"echonetlite\"],\n                    config,\n                    op_code,\n                    ENL_OP_CODES[\"default\"],\n                    hass,\n                )\n            )\n    async_add_entities(entities, True)\n\n\nclass EchonetBinarySensor(BinarySensorEntity):\n    \"\"\"Representation of an ECHONETLite Temperature Sensor.\"\"\"\n\n    _attr_translation_key = DOMAIN\n\n    def __init__(self, connector, config, op_code, attributes, hass=None) -> None:\n        \"\"\"Initialize the sensor.\"\"\"\n        name = get_device_name(connector, config)\n        self._connector = connector\n        self._op_code = op_code\n        self._sensor_attributes = attributes\n        self._eojgc = self._connector._eojgc\n        self._eojcc = self._connector._eojcc\n        self._eojci = self._connector._eojci\n        self._attr_unique_id = (\n            f\"{self._connector._uidi}-{self._op_code}\"\n            if self._connector._uidi\n            else f\"{self._connector._uid}-{self._eojgc}-{self._eojcc}-{self._eojci}-{self._op_code}\"\n        )\n        self._device_name = name\n        self._state_value = None\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._hass = hass\n\n        _attr_keys = self._sensor_attributes.keys()\n\n        self._attr_icon = self._sensor_attributes.get(CONF_ICON)\n        self._attr_device_class = self._sensor_attributes.get(CONF_TYPE)\n        self._attr_state_class = self._sensor_attributes.get(CONF_STATE_CLASS)\n\n        # Create name based on sensor description from EPC codes, super class codes or fallback to using the sensor type\n        self._attr_name = f\"{name} {get_name_by_epc_code(self._eojgc, self._eojcc, self._op_code, self._attr_device_class, self._connector._enl_op_codes.get(self._op_code, {}).get(CONF_NAME))}\"\n\n        if \"dict_key\" in _attr_keys:\n            self._attr_unique_id += f'-{self._sensor_attributes[\"dict_key\"]}'\n            if type(self._sensor_attributes[TYPE_DATA_DICT]) == int:\n                # As of Version 3.8.0, no configuration is defined that uses this definition.\n                self._attr_name += f' {str(self._sensor_attributes[\"accessor_index\"] + 1).zfill(len(str(self._sensor_attributes[TYPE_DATA_DICT])))}'\n            else:\n                self._attr_name += f' {self._sensor_attributes[\"dict_key\"]}'\n\n        if \"accessor_index\" in _attr_keys:\n            self._attr_unique_id += f'-{self._sensor_attributes[\"accessor_index\"]}'\n            self._attr_name += f' {str(self._sensor_attributes[\"accessor_index\"] + 1).zfill(len(str(self._sensor_attributes[TYPE_DATA_ARRAY_WITH_SIZE_OPCODE])))}'\n\n        self._attr_entity_registry_enabled_default = not bool(\n            self._sensor_attributes.get(CONF_DISABLED_DEFAULT)\n        )\n\n        self._attr_should_poll = True\n        self._attr_available = True\n\n        self.update_option_listener()\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._eojgc,\n                    self._connector._eojcc,\n                    self._connector._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._eojgc][self._eojcc],\n            # \"sw_version\": \"\",\n        }\n\n    def get_attr_is_on(self):\n        \"\"\"Return the state of the sensor.\"\"\"\n        if self._op_code in self._connector._update_data:\n            new_val = self._connector._update_data[self._op_code]\n            if \"dict_key\" in self._sensor_attributes:\n                if hasattr(new_val, \"get\"):\n                    self._state_value = new_val.get(self._sensor_attributes[\"dict_key\"])\n                else:\n                    self._state_value = None\n            elif \"accessor_lambda\" in self._sensor_attributes:\n                self._state_value = self._sensor_attributes[\"accessor_lambda\"](\n                    new_val, self._sensor_attributes[\"accessor_index\"]\n                )\n            else:\n                self._state_value = new_val\n\n            if self._state_value is None:\n                return None\n\n            _results = {\n                True: True,\n                \"1\": True,\n                DATA_STATE_ON: True,\n                DATA_STATE_OPEN: True,\n                \"yes\": True,\n                False: False,\n                \"0\": False,\n                DATA_STATE_OFF: False,\n                DATA_STATE_CLOSE: False,\n                \"no\": False,\n            }\n\n            return _results.get(self._state_value)\n\n    async def async_update(self):\n        \"\"\"Retrieve latest state.\"\"\"\n        try:\n            await self._connector.async_update()\n            self._attr_is_on = self.get_attr_is_on()\n        except TimeoutError:\n            pass\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush: bool = False):\n        new_val = self._connector._update_data.get(self._op_code)\n        if \"dict_key\" in self._sensor_attributes:\n            if hasattr(new_val, \"get\"):\n                new_val = new_val.get(self._sensor_attributes[\"dict_key\"])\n            else:\n                new_val = None\n        if \"accessor_lambda\" in self._sensor_attributes:\n            new_val = self._sensor_attributes[\"accessor_lambda\"](\n                new_val, self._sensor_attributes[\"accessor_index\"]\n            )\n        changed = (\n            new_val is not None and self._state_value != new_val\n        ) or self._attr_available != self._server_state[\"available\"]\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._state_value = new_val\n            self._attr_is_on = self.get_attr_is_on()\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self.async_schedule_update_ha_state(_force)\n\n    def update_option_listener(self):\n        _should_poll = self._op_code not in self._connector._ntfPropertyMap\n        self._attr_should_poll = (\n            self._connector._user_options.get(CONF_FORCE_POLLING, False) or _should_poll\n        )\n        self._attr_extra_state_attributes = {\"notify\": \"No\" if _should_poll else \"Yes\"}\n        _LOGGER.debug(\n            f\"{self._attr_name}({self._op_code}): _should_poll is {_should_poll}\"\n        )\n"
  },
  {
    "path": "custom_components/echonetlite/climate.py",
    "content": "import logging\nimport math\n\nimport voluptuous as vol\nfrom homeassistant.components.climate import (\n    ClimateEntity,\n)\nfrom homeassistant.components.climate.const import (\n    ATTR_HVAC_MODE,\n    ClimateEntityFeature,\n    HVACAction,\n    HVACMode,\n)\nfrom homeassistant.const import (\n    ATTR_TEMPERATURE,\n    PRECISION_WHOLE,\n    UnitOfTemperature,\n)\nfrom homeassistant.helpers import config_validation as cv\nfrom homeassistant.helpers import entity_platform\nfrom pychonet.HomeAirConditioner import (\n    AIRFLOW_VERT,\n    ENL_AIR_VERT,\n    ENL_AUTO_DIRECTION,\n    ENL_FANSPEED,\n    ENL_HVAC_MODE,\n    ENL_HVAC_ROOM_TEMP,\n    ENL_HVAC_SET_HUMIDITY,\n    ENL_HVAC_SET_TEMP,\n    ENL_HVAC_SILENT_MODE,\n    ENL_STATUS,\n    ENL_SWING_MODE,\n    FAN_SPEED,\n    SILENT_MODE,\n)\nfrom pychonet.lib.eojx import EOJX_CLASS\n\nfrom . import get_device_name\nfrom .const import DATA_STATE_ON, DOMAIN, OPTION_HA_UI_SWING\n\n_LOGGER = logging.getLogger(__name__)\n\nDEFAULT_FAN_MODES = list(\n    FAN_SPEED.keys()\n)  # [\"auto\",\"minimum\",\"low\",\"medium-low\",\"medium\",\"medium-high\",\"high\",\"very-high\",\"max\"]\nDEFAULT_HVAC_MODES = [\n    HVACMode.HEAT,\n    HVACMode.COOL,\n    HVACMode.DRY,\n    HVACMode.FAN_ONLY,\n    HVACMode.HEAT_COOL,\n    HVACMode.OFF,\n]\nDEFAULT_SWING_MODES = [\"auto-vert\"] + list(\n    AIRFLOW_VERT.keys()\n)  # [\"auto-vert\",\"upper\",\"upper-central\",\"central\",\"lower-central\",\"lower\"]\nDEFAULT_PRESET_MODES = list(SILENT_MODE.keys())  # [\"normal\", \"high-speed\", \"silent\"]\n\nSERVICE_SET_HUMIDIFER_DURING_HEATER = \"set_humidifier_during_heater\"\nATTR_STATE = \"state\"\nATTR_HUMIDITY = \"humidity\"\n\n\nasync def async_setup_entry(hass, config_entry, async_add_devices):\n    \"\"\"Set up entry.\"\"\"\n    entities = []\n    for entity in hass.data[DOMAIN][config_entry.entry_id]:\n        if (\n            entity[\"instance\"][\"eojgc\"] == 0x01 and entity[\"instance\"][\"eojcc\"] == 0x30\n        ):  # Home Air Conditioner\n            entities.append(EchonetClimate(entity[\"echonetlite\"], config_entry))\n    async_add_devices(entities, True)\n\n    platform = entity_platform.async_get_current_platform()\n\n    platform.async_register_entity_service(\n        SERVICE_SET_HUMIDIFER_DURING_HEATER,\n        {\n            vol.Required(ATTR_STATE): cv.boolean,\n            vol.Required(ATTR_HUMIDITY): cv.byte,\n        },\n        \"async_set_humidifier_during_heater\",\n    )\n\n\nclass EchonetClimate(ClimateEntity):\n    \"\"\"Representation of an ECHONETLite climate device.\"\"\"\n\n    _attr_translation_key = DOMAIN\n\n    def __init__(self, connector, config):\n        \"\"\"Initialize the climate device.\"\"\"\n        name = get_device_name(connector, config)\n        self._attr_name = name\n        self._device_name = name\n        self._connector = connector  # new line\n        self._attr_unique_id = (\n            self._connector._uidi if self._connector._uidi else self._connector._uid\n        )\n        # The temperature unit of echonet lite is defined as Celsius.\n        # Set temperature_unit setting to Celsius,\n        # HA's automatic temperature unit conversion function works correctly.\n        self._attr_temperature_unit = UnitOfTemperature.CELSIUS\n        self._attr_precision = PRECISION_WHOLE\n        self._attr_target_temperature_step = 1\n        if hasattr(ClimateEntityFeature, \"TURN_ON\"):\n            self._attr_supported_features = ClimateEntityFeature(\n                ClimateEntityFeature.TURN_ON | ClimateEntityFeature.TURN_OFF\n            )\n        else:\n            self._attr_supported_features = ClimateEntityFeature(0)\n        self._attr_supported_features = (\n            self._attr_supported_features | ClimateEntityFeature.TARGET_TEMPERATURE\n        )\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._opc_data = {\n            ENL_AUTO_DIRECTION: list(\n                self._connector._instance.EPC_FUNCTIONS[ENL_AUTO_DIRECTION][1].values()\n            ),\n            ENL_SWING_MODE: list(\n                self._connector._instance.EPC_FUNCTIONS[ENL_SWING_MODE][1].values()\n            ),\n        }\n        if ENL_FANSPEED in list(self._connector._setPropertyMap):\n            self._attr_supported_features = (\n                self._attr_supported_features | ClimateEntityFeature.FAN_MODE\n            )\n        if ENL_AIR_VERT in list(\n            self._connector._setPropertyMap\n        ) or ENL_SWING_MODE in list(self._connector._setPropertyMap):\n            self._attr_supported_features = (\n                self._attr_supported_features | ClimateEntityFeature.SWING_MODE\n            )\n        if ENL_HVAC_SILENT_MODE in list(self._connector._setPropertyMap):\n            self._attr_supported_features = (\n                self._attr_supported_features | ClimateEntityFeature.PRESET_MODE\n            )\n        self._attr_hvac_modes = DEFAULT_HVAC_MODES\n        self._attr_preset_modes = DEFAULT_PRESET_MODES\n        self._olddata = {}\n\n        self._last_mode = HVACMode.OFF\n\n        self._attr_should_poll = True\n        self._attr_available = True\n\n        self.update_option_listener()\n        self._set_attrs()\n\n        # see, https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded\n        self._enable_turn_on_off_backwards_compatibility = False\n\n    async def async_update(self):\n        \"\"\"Get the latest state from the HVAC.\"\"\"\n        try:\n            await self._connector.async_update()\n        except TimeoutError:\n            pass\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._instance._eojgc,\n                    self._connector._instance._eojcc,\n                    self._connector._instance._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._connector._instance._eojgc][\n                self._connector._instance._eojcc\n            ],\n            # \"sw_version\": \"\",\n        }\n\n    def _set_min_max_temp(self):\n        self._attr_min_temp = self._connector._user_options[\"min_temp_auto\"]\n        self._attr_max_temp = self._connector._user_options[\"max_temp_auto\"]\n\n        if hasattr(self, \"_attr_hvac_mode\"):\n            \"\"\"minimum/maximum temperature supported by the HVAC.\"\"\"\n            if self._attr_hvac_mode == HVACMode.HEAT:\n                self._attr_min_temp = self._connector._user_options[\"min_temp_heat\"]\n                self._attr_max_temp = self._connector._user_options[\"max_temp_heat\"]\n            elif self._attr_hvac_mode == HVACMode.COOL:\n                self._attr_min_temp = self._connector._user_options[\"min_temp_cool\"]\n                self._attr_max_temp = self._connector._user_options[\"max_temp_cool\"]\n\n    def _set_attrs(self):\n        \"\"\"current temperature.\"\"\"\n        _val = self._connector._update_data.get(ENL_HVAC_ROOM_TEMP)\n        # 0x7F: Overflow, 0x80: Underflow, 0x7E:Value cannot be returned\n        if _val in {0x7F, 0x80, 0x7E}:\n            _val = None\n        self._attr_current_temperature = _val\n\n        \"\"\"temperature we try to reach.\"\"\"\n        _val = self._connector._update_data.get(ENL_HVAC_SET_TEMP)\n        # -3: Rule of thumb, 0xFD: Temperature indeterminable\n        if _val in {-3, 0xFD}:\n            _val = None\n        self._attr_target_temperature = _val\n\n        \"\"\"temperature we try to reach.\"\"\"\n        self._attr_target_humidity = self._connector._update_data.get(\n            ENL_HVAC_SET_HUMIDITY\n        )\n\n        \"\"\"current operation ie. heat, cool, idle.\"\"\"\n        _val = self._connector._update_data.get(ENL_HVAC_MODE)\n        self._attr_hvac_mode = HVACMode.OFF\n        if self._connector._update_data[ENL_STATUS] == DATA_STATE_ON:\n            if _val == \"auto\":\n                self._attr_hvac_mode = HVACMode.HEAT_COOL\n            elif _val == \"other\":\n                if self._connector._user_options.get(ENL_HVAC_MODE) == \"as_idle\":\n                    self._attr_hvac_mode = self._last_mode\n                else:\n                    self._attr_hvac_mode = HVACMode.OFF\n            else:\n                self._attr_hvac_mode = _val\n            if self._attr_hvac_mode != HVACMode.OFF:\n                self._last_mode = self._attr_hvac_mode\n\n        \"\"\"current operation ie. heat, cool, idle.\"\"\"\n        self._attr_hvac_action = HVACAction.OFF\n        if self._connector._update_data[ENL_STATUS] == DATA_STATE_ON:\n            if self._connector._update_data[ENL_HVAC_MODE] == HVACMode.HEAT:\n                self._attr_hvac_action = HVACAction.HEATING\n            elif self._connector._update_data[ENL_HVAC_MODE] == HVACMode.COOL:\n                self._attr_hvac_action = HVACAction.COOLING\n            elif self._connector._update_data[ENL_HVAC_MODE] == HVACMode.DRY:\n                self._attr_hvac_action = HVACAction.DRYING\n            elif self._connector._update_data[ENL_HVAC_MODE] == HVACMode.FAN_ONLY:\n                self._attr_hvac_action = HVACAction.FAN\n            elif (\n                self._connector._update_data[ENL_HVAC_MODE] == HVACMode.HEAT_COOL\n                or self._connector._update_data[ENL_HVAC_MODE] == \"auto\"\n            ):\n                _room_temp = self._connector._update_data.get(ENL_HVAC_ROOM_TEMP)\n                if _room_temp := self._connector._update_data.get(ENL_HVAC_ROOM_TEMP):\n                    if self._connector._update_data[ENL_HVAC_SET_TEMP] < _room_temp:\n                        self._attr_hvac_action = HVACAction.COOLING\n                    elif self._connector._update_data[ENL_HVAC_SET_TEMP] > _room_temp:\n                        self._attr_hvac_action = HVACAction.HEATING\n                else:\n                    self._attr_hvac_action = HVACAction.IDLE\n            elif self._connector._update_data[ENL_HVAC_MODE] == \"other\":\n                if self._connector._user_options.get(ENL_HVAC_MODE) == \"as_idle\":\n                    self._attr_hvac_action = HVACAction.IDLE\n                else:\n                    self._attr_hvac_action = HVACAction.OFF\n            else:\n                _LOGGER.warning(\n                    f\"Unknown HVAC mode {self._connector._update_data.get(ENL_HVAC_MODE)}\"\n                )\n                self._attr_hvac_action = HVACAction.IDLE\n\n        \"\"\"true if the device is on.\"\"\"\n        self._attr_is_on = (\n            True if self._connector._update_data[ENL_STATUS] == DATA_STATE_ON else False\n        )\n\n        \"\"\"fan setting.\"\"\"\n        self._attr_fan_mode = (\n            self._connector._update_data[ENL_FANSPEED]\n            if ENL_FANSPEED in self._connector._update_data\n            else None\n        )\n\n        \"\"\"preset setting.\"\"\"\n        self._attr_preset_mode = (\n            self._connector._update_data[ENL_HVAC_SILENT_MODE]\n            if ENL_HVAC_SILENT_MODE in self._connector._update_data\n            else None\n        )\n\n        \"\"\"swing mode setting.\"\"\"\n        if (\n            self._connector._update_data.get(ENL_AUTO_DIRECTION)\n            in self._attr_swing_modes\n        ):\n            self._attr_swing_mode = self._connector._update_data.get(ENL_AUTO_DIRECTION)\n        elif self._connector._update_data.get(ENL_SWING_MODE) in self._attr_swing_modes:\n            self._attr_swing_mode = self._connector._update_data.get(ENL_SWING_MODE)\n        else:\n            self._attr_swing_mode = (\n                self._connector._update_data[ENL_AIR_VERT]\n                if ENL_AIR_VERT in self._connector._update_data\n                else None\n            )\n\n        self._set_min_max_temp()\n\n    async def async_set_fan_mode(self, fan_mode):\n        \"\"\"Set new fan mode.\"\"\"\n        _LOGGER.debug(f\"Updated fan mode is: {fan_mode}\")\n        await self._connector._instance.setFanSpeed(fan_mode)\n\n    async def async_set_preset_mode(self, preset_mode):\n        \"\"\"Set new preset mode - This is normal/high-speed/silent\"\"\"\n        await self._connector._instance.setSilentMode(preset_mode)\n\n    async def async_set_swing_mode(self, swing_mode):\n        \"\"\"Set new swing mode.\"\"\"\n        if swing_mode in self._opc_data[ENL_AUTO_DIRECTION]:\n            await self._connector._instance.setAutoDirection(swing_mode)\n        elif swing_mode in self._opc_data[ENL_SWING_MODE]:\n            await self._connector._instance.setSwingMode(swing_mode)\n        else:\n            await self._connector._instance.setAirflowVert(swing_mode)\n\n    async def async_set_temperature(self, **kwargs):\n        \"\"\"Set new target temperatures.\"\"\"\n        # Check has HVAC Mode\n        hvac_mode = kwargs.get(ATTR_HVAC_MODE)\n        if hvac_mode is not None:\n            await self.async_set_hvac_mode(hvac_mode)\n\n        settemp = self._normalize_settemp(kwargs.get(ATTR_TEMPERATURE))\n        if kwargs.get(ATTR_TEMPERATURE) is not None:\n            await self._connector._instance.setOperationalTemperature(settemp)\n\n    async def async_set_humidity(self, humidity: int) -> None:\n        await self._connector._instance.setOperationalTemperature(humidity)\n\n    async def async_set_hvac_mode(self, hvac_mode):\n        # _LOGGER.warning(self._connector._update_data)\n        \"\"\"Set new operation mode (including off)\"\"\"\n        if hvac_mode == \"heat_cool\":\n            await self._connector._instance.setMode(\"auto\")\n        else:\n            await self._connector._instance.setMode(hvac_mode)\n\n    async def async_turn_on(self):\n        \"\"\"Turn on.\"\"\"\n        await self._connector._instance.on()\n\n    async def async_turn_off(self):\n        \"\"\"Turn off.\"\"\"\n        await self._connector._instance.off()\n\n    async def async_set_humidifier_during_heater(self, state, humidity):\n        \"\"\"Handle boost heating service call.\"\"\"\n        await self._connector._instance.setHeaterHumidifier(state, humidity)\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush: bool = False):\n        changed = (\n            self._olddata != self._connector._update_data\n            or self._attr_available != self._server_state[\"available\"]\n        )\n        _LOGGER.debug(\n            f\"Called async_update_callback on {self._device_name}.\\nChanged: {changed}\\nUpdate data: {self._connector._update_data}\\nOld data: {self._olddata}\"\n        )\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._olddata = self._connector._update_data.copy()\n            self._attr_available = self._server_state[\"available\"]\n            self._set_attrs()\n            self.async_schedule_update_ha_state(_force | isPush)\n\n    def update_option_listener(self):\n        \"\"\"list of available fan modes.\"\"\"\n        _modes = self._connector._user_options.get(ENL_FANSPEED)\n        if _modes:\n            self._attr_fan_modes = _modes\n        else:\n            self._attr_fan_modes = DEFAULT_FAN_MODES\n\n        \"\"\"list of available swing modes.\"\"\"\n        _modes = self._connector._user_options.get(OPTION_HA_UI_SWING)\n        if _modes and len(_modes):\n            self._attr_swing_modes = _modes\n        elif _modes := self._connector._user_options.get(ENL_AIR_VERT):\n            self._attr_swing_modes = _modes\n        else:\n            self._attr_swing_modes = DEFAULT_SWING_MODES\n\n        self._set_min_max_temp()\n        if self.hass:\n            self.async_schedule_update_ha_state()\n\n    def _normalize_settemp(self, req: float | int | None) -> int | None:\n        \"\"\"\n        Normalize a requested temperature to the 1°C resolution supported by\n        ECHONET Lite HVAC devices.\n\n        Matter controllers may send fractional values (e.g., 22.5°C). Since most\n        ECHONET air conditioners accept only integer setpoints, this function\n        converts the request to a valid value while preserving user intent:\n        - Integer values are used as-is.\n        - `.5` values are rounded directionally based on the previous target\n          temperature (up when increasing, down when decreasing).\n        - Other fractions are rounded to the nearest integer.\n        \"\"\"\n        if req is None:\n            return None\n\n        res = None\n        if abs(req - round(req)) < 1e-9:\n            res = int(round(req))\n        else:\n            prev = self._attr_target_temperature\n            frac = req - math.floor(req)\n\n            if abs(frac - 0.5) < 1e-9 and prev is not None:\n                if req >= prev:\n                    res = math.ceil(req)\n                if req < prev:\n                    res = math.floor(req)\n            else:\n                res = int(math.floor(req + 0.5))\n\n        return res\n"
  },
  {
    "path": "custom_components/echonetlite/config_flow.py",
    "content": "\"\"\"Config flow for echonetlite integration.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport asyncio\nfrom typing import Any\n\nimport voluptuous as vol\n\nfrom homeassistant import config_entries\nfrom homeassistant.config_entries import ConfigEntryState, ConfigFlowResult\nfrom homeassistant.core import HomeAssistant, callback\nfrom homeassistant.data_entry_flow import FlowResult\nfrom homeassistant.exceptions import HomeAssistantError\nimport homeassistant.helpers.config_validation as cv\nfrom homeassistant.helpers.selector import selector\nfrom pychonet.lib.const import (\n    ENL_STATMAP,\n    ENL_SETMAP,\n    ENL_GETMAP,\n    GET,\n)\n\n# from aioudp import UDPServer\nfrom pychonet.lib.udpserver import UDPServer\nfrom pychonet.lib.epc_functions import _null_padded_optional_string\n\n# from pychonet import Factory\nfrom pychonet import ECHONETAPIClient\n\nfrom pychonet.HomeAirConditioner import (\n    ENL_AIR_VERT,\n    ENL_AUTO_DIRECTION,\n    ENL_SWING_MODE,\n)\n\nfrom .const import (\n    DOMAIN,\n    USER_OPTIONS,\n    TEMP_OPTIONS,\n    MISC_OPTIONS,\n    ENL_HVAC_MODE,\n    CONF_OTHER_MODE,\n    OPTION_HA_UI_SWING,\n)\n\n_LOGGER = logging.getLogger(__name__)\n\nWORD_OF_AUTO_DISCOVERY = \"[Auto Discovery]\"\n\n# TODO adjust the data schema to the data that you need\nSTEP_USER_DATA_SCHEMA = vol.Schema(\n    {\n        vol.Required(\"host\", default=WORD_OF_AUTO_DISCOVERY): str,\n        vol.Required(\"title\", default=WORD_OF_AUTO_DISCOVERY): str,\n    }\n)\n\n_detected_hosts = {}\n_init_server = None\n\n\nasync def enumerate_instances(\n    hass: HomeAssistant, host: str, newhost: bool = False\n) -> list[dict[str, Any]]:\n    \"\"\"Validate the user input allows us to connect.\"\"\"\n    _LOGGER.debug(f\"IP address is {host}\")\n    server = None\n    if DOMAIN in hass.data:  # maybe set up by config entry?\n        _LOGGER.debug(\"API listener has already been setup previously..\")\n        server = hass.data[DOMAIN][\"api\"]\n        for key in hass.data[DOMAIN]:\n            if key != \"api\":\n                entries = hass.data[DOMAIN][key]\n                if len(entries):\n                    inst = entries[0].get(\"instance\")\n                    if inst:\n                        if inst.get(\"host\") == host:\n                            raise ErrorConnect(\"already_configured\")\n    elif _init_server:\n        _LOGGER.debug(\"API listener has already been setup in init_discover()\")\n        server = _init_server\n    else:\n        udp = UDPServer()\n        udp.run(\"0.0.0.0\", 3610, loop=hass.loop)\n        server = ECHONETAPIClient(server=udp)\n        server._debug_flag = True\n        server._logger = _LOGGER.debug\n        server._message_timeout = 300\n\n    # make sure multicast is registered with the local IP used to reach this host\n    server._server.register_multicast_from_host(host)\n\n    instance_list = []\n    _LOGGER.debug(\"Beginning ECHONET node discovery\")\n    await server.discover(host)\n\n    # Timeout after 10 seconds\n    for x in range(0, 1000):\n        await asyncio.sleep(0.01)\n        if \"discovered\" in list(server._state[host]):\n            _LOGGER.debug(\"ECHONET Node Discovery Successful!\")\n            break\n    if \"discovered\" not in list(server._state[host]):\n        _LOGGER.debug(\"ECHONET Node Discovery Failed!\")\n        raise ErrorConnect(\"cannot_connect\")\n    state = server._state[host]\n    uid = state[\"uid\"]\n\n    # check ip addr changed\n    if newhost:\n        config_entry = None\n        old_host = None\n        entries = hass.config_entries.async_entries(DOMAIN)\n        entry = None\n        instances = None\n\n        for entry in entries:\n            instances = []\n            _data = entry.data\n            for _instance in _data.get(\"instances\", []):\n                instance = _instance.copy()\n                if old_host or instance.get(\"uid\") == uid:\n                    old_host = instance[\"host\"]\n                    instance[\"host\"] = host\n                instances.append(instance)\n            if old_host:\n                config_entry = entry\n                _LOGGER.debug(\n                    f\"ECHONET registed node found uid is {uid}, conig entry id is {entry.entry_id}.\"\n                )\n                break\n\n        if old_host and entry and instances and config_entry:\n            _LOGGER.debug(\n                f\"ECHONET registed node IP changed from {old_host} to {host}.\"\n            )\n            _LOGGER.debug(f\"New instances data is {instances}\")\n            if server._state.get(old_host):\n                server._state[host] = server._state.pop(old_host)\n            hass.config_entries.async_update_entry(\n                config_entry, data={\"host\": host, \"instances\": instances}\n            )\n\n            # Wait max 30 secs for entry loaded\n            for x in range(0, 300):\n                await asyncio.sleep(0.1)\n                if entry.state == ConfigEntryState.LOADED:\n                    await hass.config_entries.async_reload(entry.entry_id)\n                    break\n\n            raise ErrorIpChanged(host)\n\n    manufacturer = state[\"manufacturer\"]\n    host_product_code = state.get(\"product_code\")\n    if type(host_product_code) == str:\n        host_product_code = str.strip(host_product_code)\n    if not isinstance(manufacturer, str):\n        # If unable to resolve the manufacturer,\n        # the raw identification number will be passed as int.\n        _LOGGER.warn(\n            f\"{host} - Unable to resolve the manufacturer name - {manufacturer}. \"\n            + \"Please report the manufacturer name of your device at the issue tracker on GitHub!\"\n        )\n        manufacturer = f\"Unknown({manufacturer})\"\n\n    for eojgc in list(state[\"instances\"].keys()):\n        for eojcc in list(state[\"instances\"][eojgc].keys()):\n            for instance in list(state[\"instances\"][eojgc][eojcc].keys()):\n                _LOGGER.debug(f\"instance is {instance}\")\n\n                cnt = 0\n                while (\n                    await server.getAllPropertyMaps(host, eojgc, eojcc, instance)\n                    is False\n                ):\n                    cnt += 1\n                    if cnt > 2:\n                        raise ErrorConnect(\"cannot_get_property_maps\")\n\n                _LOGGER.debug(\n                    f\"{host} - ECHONET Instance {eojgc}-{eojcc}-{instance} map attributes discovered!\"\n                )\n                ntfmap = state[\"instances\"][eojgc][eojcc][instance].get(ENL_STATMAP, [])\n                getmap = state[\"instances\"][eojgc][eojcc][instance][ENL_GETMAP]\n                setmap = state[\"instances\"][eojgc][eojcc][instance][ENL_SETMAP]\n\n                uidi = f\"{uid}-{eojgc}-{eojcc}-{instance}\"\n                name = None\n                if host_product_code == \"WTY2001\" and eojcc == 0x91:\n                    # Panasonic WTY2001 Advanced Series Link Plus Wireless Adapter\n                    await server.echonetMessage(\n                        host,\n                        eojgc,\n                        eojcc,\n                        instance,\n                        GET,\n                        [{\"EPC\": 0xFD}, {\"EPC\": 0xFE}],\n                    )\n                    # Use Use HW ID because the instance number is uncertain\n                    # https://github.com/scottyphillips/echonetlite_homeassistant/issues/117#issuecomment-1929151918\n                    uidi = _null_padded_optional_string(\n                        state[\"instances\"][eojgc][eojcc][instance][0xFE]\n                    )\n                    name = _null_padded_optional_string(\n                        state[\"instances\"][eojgc][eojcc][instance][0xFD]\n                    )\n\n                instance_list.append(\n                    {\n                        \"host\": host,\n                        \"name\": name,\n                        \"eojgc\": eojgc,\n                        \"eojcc\": eojcc,\n                        \"eojci\": instance,\n                        \"ntfmap\": ntfmap,\n                        \"getmap\": getmap,\n                        \"setmap\": setmap,\n                        \"uid\": uid,  # Deprecated, for backwards compatibility\n                        \"uidi\": uidi,\n                        \"manufacturer\": manufacturer,\n                        \"host_product_code\": host_product_code,\n                    }\n                )\n\n    return instance_list\n\n\nasync def async_discover_newhost(hass, host):\n    _LOGGER.debug(f\"received newip discovery: {host}\")\n    if host not in _detected_hosts.keys():\n        try:\n            instance_list = await enumerate_instances(hass, host, newhost=True)\n            _LOGGER.debug(f\"ECHONET Node detected in {host}\")\n        except ErrorConnect as e:\n            _LOGGER.debug(f\"ECHONET Node Error Connect ({e})\")\n        except ErrorIpChanged as e:\n            _LOGGER.debug(f\"ECHONET Detected Node IP Changed to '{e}'\")\n        else:\n            if len(instance_list):\n                _detected_hosts.update({host: instance_list})\n            else:\n                _LOGGER.debug(f\"ECHONET Node not found in {host}\")\n\n\nclass ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):\n    \"\"\"Handle a config flow for echonetlite.\"\"\"\n\n    host = None\n    title = \"\"\n    discover_task = None\n    instance_list = None\n    instances = None\n    VERSION = 1\n\n    async def init_discover(self):\n        async def discover_callback(host):\n            await async_discover_newhost(self.hass, host)\n\n        if DOMAIN in self.hass.data:  # maybe set up by config entry?\n            _LOGGER.debug(\"API listener has already been setup previously..\")\n            server = self.hass.data[DOMAIN][\"api\"]\n\n            _init_server = None\n\n        else:\n            udp = UDPServer()\n            udp.run(\"0.0.0.0\", 3610, loop=self.hass.loop)\n            server = ECHONETAPIClient(server=udp)\n            server._debug_flag = True\n            server._logger = _LOGGER.debug\n            server._message_timeout = 300\n            server._discover_callback = discover_callback\n\n            _init_server = server\n\n        await server.discover()\n\n        # Timeout after 30 seconds\n        for x in range(0, 3000):\n            await asyncio.sleep(0.01)\n            if len(_detected_hosts):\n                _LOGGER.debug(\"ECHONET Any Node Discovery Successful!\")\n                break\n\n        if _init_server:\n            _init_server._server._sock.close()\n            del _init_server\n            _init_server = None\n\n    async def async_step_user(\n        self, user_input: dict[str, Any] | None = None\n    ) -> ConfigFlowResult:\n        errors = {}\n        \"\"\"Handle the initial step.\"\"\"\n        scm = STEP_USER_DATA_SCHEMA\n        if user_input is None or user_input.get(\"host\") == WORD_OF_AUTO_DISCOVERY:\n            step = \"user_man\"\n            if (\n                user_input\n                and user_input.get(\"host\") == WORD_OF_AUTO_DISCOVERY\n                and not len(_detected_hosts)\n            ):\n                await self.init_discover()\n            if len(_detected_hosts):\n                host = list(_detected_hosts.keys()).pop(0)\n                title = _detected_hosts[host][0][\"manufacturer\"]\n                if _detected_hosts[host][0][\"host_product_code\"]:\n                    title += \" \" + _detected_hosts[host][0][\"host_product_code\"]\n            else:\n                if user_input is None:\n                    host = title = WORD_OF_AUTO_DISCOVERY\n                    step = \"user\"\n                else:\n                    host = \"\"\n                    title = \"\"\n                    errors[\"base\"] = \"not_found\"\n            scm = scm.extend(\n                {\n                    vol.Required(\"host\", default=host): str,\n                    vol.Required(\"title\", default=title): str,\n                }\n            )\n            return self.async_show_form(step_id=step, data_schema=scm, errors=errors)\n        try:\n            self.instance_list = await enumerate_instances(\n                self.hass, user_input[\"host\"]\n            )\n            _LOGGER.debug(\"Node detected\")\n        except ErrorConnect as e:\n            errors[\"base\"] = f\"{e}\"\n        else:\n            self.host = user_input[\"host\"]\n            self.title = user_input[\"title\"]\n            return await self.async_step_finish(user_input)\n\n        return self.async_show_form(\n            step_id=\"user\", data_schema=STEP_USER_DATA_SCHEMA, errors=errors\n        )\n\n    async def async_step_user_man(\n        self, user_input: dict[str, Any] | None = None\n    ) -> FlowResult:\n        return await self.async_step_user(user_input)\n\n    async def async_step_finish(self, user_input=None):\n        if len(_detected_hosts) and self.host in _detected_hosts.keys():\n            _detected_hosts.pop(self.host)\n        return self.async_create_entry(\n            title=self.title,\n            data={\"host\": self.host, \"instances\": self.instance_list},\n            options={\"other_mode\": \"as_off\"},\n        )\n\n    @staticmethod\n    @callback\n    def async_get_options_flow(config_entry):\n        return OptionsFlowHandler(config_entry)\n\n\nclass ErrorConnect(HomeAssistantError):\n    \"\"\"Error to indicate we cannot connect.\"\"\"\n\n\nclass ErrorIpChanged(HomeAssistantError):\n    \"\"\"Error to indicate we cannot connect.\"\"\"\n\n\nclass OptionsFlowHandler(config_entries.OptionsFlow):\n    def __init__(self, config):\n        self._config_entry = config\n        self._data = {}\n\n    async def async_step_init(self, user_input=None):\n        \"\"\"Manage the options.\"\"\"\n        data_schema_structure = {}\n\n        # Handle HVAC and Air Cleaner User configurable options\n        for instance in self._config_entry.data[\"instances\"]:\n            if (\n                instance[\"eojgc\"] == 0x01 and instance[\"eojcc\"] == 0x30\n            ):  # HomeAirConditioner\n                ha_swing_list = []\n                for option in list(USER_OPTIONS.keys()):\n                    if option in instance[\"setmap\"]:\n                        if option in [ENL_AIR_VERT, ENL_AUTO_DIRECTION, ENL_SWING_MODE]:\n                            ha_swing_list.append(option)\n                        if (\n                            self._config_entry.options.get(\n                                USER_OPTIONS[option][\"option\"]\n                            )\n                            is not None\n                        ):\n                            option_default = self._config_entry.options.get(\n                                USER_OPTIONS[option][\"option\"]\n                            )\n                        else:\n                            if isinstance(USER_OPTIONS[option][\"option_list\"], list):\n                                # single select\n                                option_default = USER_OPTIONS[option][\"option_list\"][0][\n                                    \"value\"\n                                ]\n                            elif isinstance(USER_OPTIONS[option][\"option_list\"], dict):\n                                # multi selectable\n                                option_default = list(\n                                    USER_OPTIONS[option][\"option_list\"].keys()\n                                )\n                            else:\n                                option_default = []\n                        data_schema_structure.update(\n                            {\n                                vol.Optional(\n                                    USER_OPTIONS[option][\"option\"],\n                                    default=option_default,\n                                ): cv.multi_select(USER_OPTIONS[option][\"option_list\"])\n                            }\n                        )\n\n                # Handle setting Climate entity UI swing mode\n                if len(ha_swing_list) > 0:\n                    option_list = {}\n                    for opt in ha_swing_list:\n                        option_list.update(USER_OPTIONS[opt][\"option_list\"])\n                    if ENL_AIR_VERT in instance[\"setmap\"]:\n                        for del_key in [\n                            \"auto\",\n                            \"non-auto\",\n                            \"auto-horiz\",\n                            \"not-used\",\n                            \"horiz\",\n                            \"vert-horiz\",\n                        ]:\n                            option_list.pop(del_key, None)\n                    if self._config_entry.options.get(OPTION_HA_UI_SWING) is not None:\n                        option_default = self._config_entry.options.get(\n                            OPTION_HA_UI_SWING\n                        )\n                    else:\n                        option_default = list(option_list.keys())\n                    data_schema_structure.update(\n                        {\n                            vol.Optional(\n                                OPTION_HA_UI_SWING,\n                                default=option_default,\n                            ): cv.multi_select(option_list)\n                        }\n                    )\n\n                # Handle setting temperature ranges for various modes of operation\n                for option in list(TEMP_OPTIONS.keys()):\n                    default_temp = TEMP_OPTIONS[option][\"min\"]\n                    if self._config_entry.options.get(option) is not None:\n                        default_temp = self._config_entry.options.get(option)\n                    else:\n                        default_temp = TEMP_OPTIONS[option][\"default\"]\n                    data_schema_structure.update(\n                        {\n                            vol.Required(option, default=default_temp): vol.All(\n                                vol.Coerce(int),\n                                vol.Range(\n                                    min=TEMP_OPTIONS[option][\"min\"],\n                                    max=TEMP_OPTIONS[option][\"max\"],\n                                ),\n                            )\n                        }\n                    )\n\n                # Handle setting for the operation mode \"Other\"\n                option_default = \"as_off\"\n                if self._config_entry.options.get(CONF_OTHER_MODE) is not None:\n                    option_default = self._config_entry.options.get(CONF_OTHER_MODE)\n                data_schema_structure.update(\n                    {\n                        vol.Optional(\n                            USER_OPTIONS[ENL_HVAC_MODE][\"option\"],\n                            default=option_default,\n                        ): selector(\n                            {\n                                \"select\": {\n                                    \"options\": USER_OPTIONS[ENL_HVAC_MODE][\n                                        \"option_list\"\n                                    ],\n                                    \"mode\": \"dropdown\",\n                                }\n                            }\n                        )\n                    }\n                )\n\n            elif instance[\"eojgc\"] == 0x01 and instance[\"eojcc\"] == 0x35:  # AirCleaner\n                for option in list(USER_OPTIONS.keys()):\n                    if option in instance[\"setmap\"]:\n                        option_default = []\n                        if (\n                            self._config_entry.options.get(\n                                USER_OPTIONS[option][\"option\"]\n                            )\n                            is not None\n                        ):\n                            option_default = self._config_entry.options.get(\n                                USER_OPTIONS[option][\"option\"]\n                            )\n                        data_schema_structure.update(\n                            {\n                                vol.Optional(\n                                    USER_OPTIONS[option][\"option\"],\n                                    default=option_default,\n                                ): cv.multi_select(USER_OPTIONS[option][\"option_list\"])\n                            }\n                        )\n\n        for key, option in MISC_OPTIONS.items():\n            if \"min\" in option and \"max\" in option:\n                _type = vol.All(\n                    vol.Coerce(option[\"type\"]),\n                    vol.Range(min=option[\"min\"], max=option[\"max\"]),\n                )\n            else:\n                _type = option[\"type\"]\n            if type(option[\"default\"]) == list and type(option[\"default\"][0]) == dict:\n                option_default = None\n                for instance in self._config_entry.data[\"instances\"]:\n                    option_default = (\n                        option[\"default\"][0]\n                        .get(instance[\"eojgc\"], {})\n                        .get(instance[\"eojcc\"])\n                    )\n                    if option_default != None:\n                        break\n                if option_default == None:\n                    option_default = option[\"default\"][1]\n            else:\n                option_default = option[\"default\"]\n            data_schema_structure.update(\n                {\n                    vol.Required(\n                        key,\n                        default=self._config_entry.options.get(key, option_default),\n                    ): _type\n                }\n            )\n\n        if user_input is not None or not any(data_schema_structure):\n            if user_input is not None:\n                self._data.update(user_input)\n            return self.async_create_entry(title=\"\", data=self._data)\n            # return await self.async_step_misc()\n        return self.async_show_form(\n            step_id=\"init\",\n            data_schema=vol.Schema(data_schema_structure),\n        )\n\n    async def async_step_misc(self, user_input=None):\n        \"\"\"Manage the options.\"\"\"\n        data_schema_structure = {}\n\n        # for key, option in MISC_OPTIONS.items():\n        #     data_schema_structure.update({\n        #         vol.Required(\n        #             CONF_FORCE_POLLING,\n        #             default=self._config_entry.options.get(key, option['default'])\n        #         ): option['type']\n        #     })\n\n        if user_input is not None:\n            self._data.update(user_input)\n            return self.async_create_entry(title=\"\", data=self._data)\n        return self.async_show_form(\n            step_id=\"misc\",\n            data_schema=vol.Schema(data_schema_structure),\n        )\n"
  },
  {
    "path": "custom_components/echonetlite/const.py",
    "content": "\"\"\"Constants for the echonetlite integration.\"\"\"\n\nfrom homeassistant.const import (\n    CONF_ICON,\n    CONF_SERVICE,\n    CONF_TYPE,\n    CONF_SERVICE_DATA,\n    CONF_UNIT_OF_MEASUREMENT,\n    CONF_NAME,\n    CONF_MINIMUM,\n    CONF_MAXIMUM,\n    PERCENTAGE,\n    UnitOfEnergy,\n    UnitOfTime,\n    UnitOfVolume,\n    UnitOfVolumeFlowRate,\n)\nfrom homeassistant.components.binary_sensor import BinarySensorDeviceClass\nfrom homeassistant.components.sensor.const import (\n    CONF_STATE_CLASS,\n    SensorStateClass,\n    SensorDeviceClass,\n)\nfrom homeassistant.components.number.const import (\n    NumberDeviceClass,\n)\nfrom pychonet.ElectricBlind import (\n    ENL_OPENING_LEVEL,\n    ENL_BLIND_ANGLE,\n    ENL_OPENCLOSE_STATUS,\n)\nfrom pychonet.GeneralLighting import ENL_BRIGHTNESS, ENL_COLOR_TEMP\nfrom pychonet.HomeAirConditioner import (\n    ENL_HVAC_MODE,\n    ENL_FANSPEED,\n    ENL_AIR_VERT,\n    ENL_AIR_HORZ,\n    ENL_AUTO_DIRECTION,\n    ENL_HVAC_SET_TEMP,\n    ENL_HVAC_SILENT_MODE,\n    ENL_SWING_MODE,\n    FAN_SPEED,\n    AIRFLOW_VERT,\n    AIRFLOW_HORIZ,\n    AUTO_DIRECTION,\n    SWING_MODE,\n)\nfrom pychonet.EchonetInstance import ENL_STATUS, ENL_ON, ENL_OFF\nfrom pychonet.lib.const import (\n    ENL_CUMULATIVE_POWER,\n    ENL_FAULT_DESCRIPTION,\n    ENL_FAULT_STATUS,\n    ENL_INSTANTANEOUS_POWER,\n)\nfrom pychonet.lib.epc_functions import DATA_STATE_CLOSE, DATA_STATE_OPEN\nfrom pychonet.CeilingFan import (\n    ENL_BUZZER,\n    ENL_FANSPEED_PERCENT,\n    ENL_FAN_DIRECTION,\n    ENL_FAN_LIGHT_BRIGHTNESS,\n    ENL_FAN_LIGHT_COLOR_TEMP,\n    ENL_FAN_LIGHT_MODE,\n    ENL_FAN_LIGHT_NIGHT_BRIGHTNESS,\n    ENL_FAN_LIGHT_STATUS,\n    ENL_FAN_OSCILLATION,\n)\n\nDOMAIN = \"echonetlite\"\nCONF_ENSURE_ON = \"ensureon\"\nCONF_OTHER_MODE = \"other_mode\"\nCONF_FORCE_POLLING = \"force_polling\"\nCONF_ENABLE_SUPER_ENERGY = \"super_energy\"\nCONF_BATCH_SIZE_MAX = \"batch_size_max\"\nCONF_ON_VALUE = \"on_val\"\nCONF_OFF_VALUE = \"off_val\"\nCONF_DISABLED_DEFAULT = \"disabled_default\"\nCONF_MULTIPLIER = \"multiplier\"\nCONF_MULTIPLIER_OPCODE = \"multiplier_opcode\"\nCONF_MULTIPLIER_OPTIONAL_OPCODE = \"multiplier_optional_opcode\"\nCONF_ICON_POSITIVE = \"icon_positive\"\nCONF_ICON_NEGATIVE = \"icon_negative\"\nCONF_ICON_ZERO = \"icon_zero\"\nCONF_ICONS = \"icons\"\nCONF_AS_ZERO = \"as_zero\"\nCONF_MAX_OPC = \"max_opc\"\nCONF_BYTE_LENGTH = \"byte_len\"\n\nDATA_STATE_ON = \"on\"\nDATA_STATE_OFF = \"off\"\nTYPE_SWITCH = \"switch\"\nTYPE_SELECT = \"select\"\nTYPE_TIME = \"time\"\nTYPE_NUMBER = \"number\"\nTYPE_DATA_DICT = \"type_data_dict\"\nTYPE_DATA_ARRAY_WITH_SIZE_OPCODE = \"type_data_array_with_size_opcode\"\nSERVICE_SET_ON_TIMER_TIME = \"set_on_timer_time\"\nSERVICE_SET_INT_1B = \"set_value_int_1b\"\nOPEN = \"open\"\nCLOSE = \"close\"\nSTOP = \"stop\"\nDEVICE_CLASS_ECHONETLITE_LIGHT_SCENE = \"echonetlite_light_scene\"\nSWITCH_POWER = {DATA_STATE_ON: ENL_ON, DATA_STATE_OFF: ENL_OFF}\nSWITCH_BINARY = {DATA_STATE_ON: 0x41, DATA_STATE_OFF: 0x42}\nSWITCH_BINARY_INVERT = {DATA_STATE_ON: 0x42, DATA_STATE_OFF: 0x41}\n\nHVAC_SELECT_OP_CODES = {\n    0xA0: FAN_SPEED,\n    0xA1: AUTO_DIRECTION,\n    0xA3: SWING_MODE,\n    0xA5: AIRFLOW_HORIZ,\n    0xA4: AIRFLOW_VERT,\n}\n\nFAN_SELECT_OP_CODES = {0xA0: FAN_SPEED}\n\nCOVER_SELECT_OP_CODES = {0xE0: {OPEN: 0x41, CLOSE: 0x42, STOP: 0x43}}\n\nENL_TIMER_SETTING = 0x97\nENL_SUPER_CODES = {\n    ENL_STATUS: {CONF_TYPE: BinarySensorDeviceClass.POWER},\n    ENL_INSTANTANEOUS_POWER: {\n        CONF_TYPE: SensorDeviceClass.POWER,\n        CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n    },\n    ENL_CUMULATIVE_POWER: {\n        CONF_TYPE: SensorDeviceClass.ENERGY,\n        CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n        CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n        CONF_MULTIPLIER: 0.001,\n    },\n    ENL_TIMER_SETTING: {\n        CONF_ICON: \"mdi:clock-outline\",\n        TYPE_TIME: True,\n    },\n    ENL_FAULT_STATUS: {CONF_TYPE: BinarySensorDeviceClass.PROBLEM},\n    ENL_FAULT_DESCRIPTION: {\n        CONF_TYPE: SensorDeviceClass.ENUM,\n        TYPE_DATA_DICT: [\"fault classification\", \"error code\"],\n    },\n}\n\nENL_SUPER_ENERGES = {ENL_INSTANTANEOUS_POWER, ENL_CUMULATIVE_POWER}\n\nENL_OP_CODES = {\n    0x00: {  # Sensor-related Device\n        0x08: {  # Visitor sensor class\n            0xB0: {\n                CONF_ICON: \"mdi:motion-sensor\",\n            },  # Detection threshold level\n            0xB1: {\n                CONF_ICON: \"mdi:motion-sensor\",\n            },  # Visitor detection status\n            0xBE: {\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.DURATION,\n                    CONF_UNIT_OF_MEASUREMENT: UnitOfTime.SECONDS,\n                    CONF_MINIMUM: 0,\n                    CONF_MAXIMUM: 0xFFFD,\n                    CONF_MULTIPLIER: 10,\n                },\n            },  # Visitor detection holding time\n        },\n        0x11: {  # Temperature sensor\n            0xE0: {\n                CONF_ICON: \"mdi:thermometer\",\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n        },\n    },\n    0x01: {  # Air Conditioner-related Device\n        0x30: {  # Home air conditioner\n            # 0xB3: {  # for develop test\n            #     CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n            #     CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            #     TYPE_NUMBER: {  # Make Number input entity if settable value\n            #         CONF_TYPE: NumberDeviceClass.TEMPERATURE,  # NumberDeviceClass.x\n            #         CONF_AS_ZERO: 0x1,  # Value as zero\n            #         CONF_MINIMUM: 0x0,  # Minimum value\n            #         CONF_MAXIMUM: 0x32,  # Maximum value\n            #         CONF_MAX_OPC: None,  # OPC of max value\n            #         CONF_BYTE_LENGTH: 0x1,  # Data byte length\n            #         TYPE_SWITCH: {  #  Additional switch\n            #             CONF_NAME: \"Auto\",  # Additionale name\n            #             CONF_ICON: \"mdi:thermometer\",\n            #             CONF_SERVICE_DATA: {DATA_STATE_ON: 23, DATA_STATE_OFF: 22},\n            #         },\n            #     },\n            # },\n            0xB4: {  # Humidity setting in dry mode\n                CONF_TYPE: SensorDeviceClass.HUMIDITY,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.HUMIDITY,\n                    CONF_MINIMUM: 30,\n                    CONF_MAXIMUM: 90,\n                },\n                CONF_SERVICE: [\n                    SERVICE_SET_INT_1B\n                ],  # For backward compatibility (Deprecated)\n            },\n            0xBA: {\n                CONF_TYPE: SensorDeviceClass.HUMIDITY,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xBE: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xBB: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xA0: {\n                CONF_ICON: \"mdi:fan\",\n            },\n            0xA1: {\n                CONF_ICON: \"mdi:shuffle-variant\",\n            },\n            0xA3: {\n                CONF_ICON: \"mdi:arrow-oscillating\",\n            },\n            0xA5: {\n                CONF_ICON: \"mdi:tailwind\",\n            },\n            0xA4: {\n                CONF_ICON: \"mdi:tailwind\",\n            },\n        },\n        0x35: {  # Air cleaner\n            0xE1: {\n                CONF_ICON: \"mdi:air-filter\",\n            },\n            0xA0: {\n                CONF_ICON: \"mdi:fan\",\n            },\n            0xC1: {\n                CONF_ICON: \"mdi:smoking\",\n            },\n            0xC2: {\n                CONF_ICON: \"mdi:weather-sunny\",\n            },\n            0xC0: {\n                CONF_ICON: \"mdi:flower-pollen\",\n            },\n        },\n    },\n    0x02: {  # Housing/Facilities-related Device\n        0x60: {  # Electrically operated blind/shade\n            0xE0: {  # Configured as Cover but left for backward compatibility\n                CONF_ICON: \"mdi:roller-shade\",\n                CONF_ICONS: {\n                    OPEN: \"mdi:roller-shade\",\n                    CLOSE: \"mdi:roller-shade-closed\",\n                    STOP: \"mdi:roller-shade\",\n                },\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xD2: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfTime.SECONDS,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x00,\n                    CONF_MAXIMUM: 0xFD,\n                },\n            },  # Operation time\n        },\n        0x61: {  # Electrically operated shutter\n            0xE0: {  # Configured as Cover but left for backward compatibility\n                CONF_ICON: \"mdi:window-shutter-open\",\n                CONF_ICONS: {\n                    OPEN: \"mdi:window-shutter-open\",\n                    CLOSE: \"mdi:window-shutter\",\n                    STOP: \"mdi:window-shutter-open\",\n                },\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xD2: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfTime.SECONDS,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x00,\n                    CONF_MAXIMUM: 0xFD,\n                },\n            },  # Operation time\n        },\n        0x62: {  # Electrically operated curtain\n            0xE0: {  # Configured as Cover but left for backward compatibility\n                CONF_ICON: \"mdi:curtains\",\n                CONF_ICONS: {\n                    OPEN: \"mdi:curtains\",\n                    CLOSE: \"mdi:curtains-closed\",\n                    STOP: \"mdi:curtains\",\n                },\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xD2: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfTime.SECONDS,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x00,\n                    CONF_MAXIMUM: 0xFD,\n                },\n            },  # Operation time\n        },\n        0x63: {  # Electrically operated rain sliding door/shutter\n            0xE0: {  # Configured as Cover but left for backward compatibility\n                CONF_ICON: \"mdi:door-sliding-open\",\n                CONF_ICONS: {\n                    OPEN: \"mdi:door-sliding-open\",\n                    CLOSE: \"mdi:door-sliding\",\n                    STOP: \"mdi:door-sliding-open\",\n                },\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xD2: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfTime.SECONDS,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x00,\n                    CONF_MAXIMUM: 0xFD,\n                },\n            },  # Operation time\n        },\n        0x64: {  # Electrically operated gate\n            0xE0: {  # Configured as Cover but left for backward compatibility\n                CONF_ICON: \"mdi:boom-gate-up-outline\",\n                CONF_ICONS: {\n                    OPEN: \"mdi:boom-gate-up-outline\",\n                    CLOSE: \"mdi:boom-gate-outline\",\n                    STOP: \"mdi:boom-gate-up-outline\",\n                },\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xD2: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfTime.SECONDS,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x00,\n                    CONF_MAXIMUM: 0xFD,\n                },\n            },  # Operation time\n        },\n        0x65: {  # Electrically operated window\n            0xE0: {  # Configured as Cover but left for backward compatibility\n                CONF_ICON: \"mdi:window-open-variant\",\n                CONF_ICONS: {\n                    OPEN: \"mdi:window-open-variant\",\n                    CLOSE: \"mdi:window-closed-variant\",\n                    STOP: \"mdi:window-open-variant\",\n                },\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xD2: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfTime.SECONDS,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x00,\n                    CONF_MAXIMUM: 0xFD,\n                },\n            },  # Operation time\n        },\n        0x66: {  # Automatically operated entrance door/sliding door\n            0xE0: {  # Configured as Cover but left for backward compatibility\n                CONF_ICON: \"mdi:door-sliding-open\",\n                CONF_ICONS: {\n                    OPEN: \"mdi:door-sliding-open\",\n                    CLOSE: \"mdi:door-sliding\",\n                    STOP: \"mdi:door-sliding-open\",\n                },\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xD2: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfTime.SECONDS,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x00,\n                    CONF_MAXIMUM: 0xFD,\n                },\n            },  # Operation time\n        },\n        0x6B: {  # Electric water heater\n            # 0xB0: , # \"Automatic water heating setting\",\n            # 0xB1: , # \"Automatic water temperature control setting\",\n            # 0xB2: , # \"Water heater status\",\n            0xB3: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 30,\n                    CONF_MAXIMUM: 90,\n                },\n            },  # \"Water heating temperature setting\",\n            0xB4: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },  # \"Manual water heating stop days setting\",\n            # 0xB5: , # \"Relative time setting value for manual water heating OFF\",\n            # 0xB6: , # Tank operation mode setting\",\n            # 0xC0: , # Daytime reheating permission setting\",\n            0xC1: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },  # Measured temperature of water in water heater\",\n            # 0xC2: , # Alarm status\",\n            # 0xC3: , # Hot water supply status\",\n            # 0xC4: , # Relative time setting for keeping bath temperature\",\n            0xD1: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 30,\n                    CONF_MAXIMUM: 90,\n                },\n            },  # Temperature of supplied water setting\",\n            0xD3: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 30,\n                    CONF_MAXIMUM: 90,\n                },\n            },  # Bath water temperature setting\",\n            0xE0: {\n                CONF_UNIT_OF_MEASUREMENT: PERCENTAGE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 100,\n                },\n            },  # Bath water volume setting\",\n            0xE1: {\n                CONF_TYPE: SensorDeviceClass.WATER,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n            },  # Measured amount of water remaining in tank\",\n            0xE2: {\n                CONF_TYPE: SensorDeviceClass.WATER,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n            },  # Tank capacity\",\n            # 0xE3: , # Automatic bath water heating mode setting\",\n            # 0xE9: , # Bathroom priority setting\",\n            # 0xEA: , # Bath operation status monitor\",\n            # 0xE4: , # Manual bath reheating operation setting\",\n            # 0xE5: , # Manual bath hot water addition function setting\",\n            # 0xE6: , # Manual slight bath water temperature lowering function setting\",\n            0xE7: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0xFD,\n                },\n            },  # Bath water volume setting 1\",\n            0xE8: {\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_AS_ZERO: 0x30,\n                    CONF_MINIMUM: 0x31,\n                    CONF_MAXIMUM: 0x38,\n                },\n            },  # Bath water volume setting 2\",\n            0xEE: {\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0xFFFD,\n                    CONF_BYTE_LENGTH: 2,\n                },\n            },  # Bath water volume setting 3\",\n            0xD4: {\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_MINIMUM: 0x01,\n                    CONF_MAXIMUM: 0xFF,\n                    CONF_MAX_OPC: 0xD5,\n                },\n            },  # Bath water volume setting 4\",\n            0x90: {\n                CONF_ICON: \"mdi:timer\",\n            },  # ON timer reservation setting\",\n            0x91: {\n                CONF_ICON: \"mdi:timer-outline\",\n            },  # ON timer setting\",\n            0xD6: {\n                CONF_UNIT_OF_MEASUREMENT: PERCENTAGE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x64,\n                },\n            },  # Volume setting\",\n            # 0xD7: , # Mute setting\",\n            0xD8: {\n                CONF_TYPE: SensorDeviceClass.WATER,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n            },  # Remaining hot water volume\",\n            0xDB: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },  # Rated power consumption of H/P unit in wintertime\",\n            0xDC: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },  # Rated power consumption of H/P unit in in-between seasons\",\n            0xDD: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },  # Rated power consumption of H/P unit in summertime\",\n            0xCB: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_DATA_DICT: [\"10:00\", \"13:00\", \"15:00\", \"17:00\"],\n            },\n            0xCC: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_DATA_DICT: [\"10:00\", \"13:00\", \"15:00\", \"17:00\"],\n            },\n            0xCE: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_DATA_DICT: [\"13:00\", \"15:00\", \"17:00\"],\n            },\n            0xCF: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_DATA_DICT: [\"13:00\", \"15:00\", \"17:00\"],\n            },\n        },\n        0x6F: {  # Electric lock\n            0xE0: {\n                CONF_ICON: \"mdi:lock\",\n                CONF_ENSURE_ON: ENL_STATUS,\n            },\n            0xE1: {\n                CONF_ICON: \"mdi:lock\",\n                CONF_ENSURE_ON: ENL_STATUS,\n            },\n            0xE6: {\n                CONF_ICON: None,\n                CONF_ENSURE_ON: ENL_STATUS,\n            },\n        },\n        0x72: {  # Hot water generator\n            0x90: {\n                CONF_ICON: \"mdi:timer\",\n            },\n            0x91: {  # Sensor with service\n                CONF_ICON: \"mdi:timer-outline\",\n                CONF_SERVICE: [\n                    SERVICE_SET_ON_TIMER_TIME\n                ],  # For backward compatibility (Deprecated)\n            },\n            0xD1: {  # Sensor\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 30,\n                    CONF_MAXIMUM: 90,\n                },\n                CONF_SERVICE: [\n                    SERVICE_SET_INT_1B\n                ],  # For backward compatibility (Deprecated)\n            },\n            0xE1: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 30,\n                    CONF_MAXIMUM: 90,\n                },\n                CONF_SERVICE: [\n                    SERVICE_SET_INT_1B\n                ],  # For backward compatibility (Deprecated)\n            },\n            0xE3: {\n                CONF_ICON: \"mdi:bathtub-outline\",\n            },\n            0xE4: {\n                CONF_ICON: \"mdi:heat-wave\",\n            },\n            0xE7: {\n                CONF_TYPE: SensorDeviceClass.WATER,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n            },\n            0xEE: {\n                CONF_TYPE: SensorDeviceClass.WATER,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n            },\n        },\n        0x73: {  # Bathroom dryer\n            0xBA: {\n                CONF_TYPE: SensorDeviceClass.HUMIDITY,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xBB: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xE0: {\n                CONF_ICON: \"mdi:motion-sensor\",\n            },\n        },\n        0x79: {  # Home solar power generation\n            0xA0: {\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                CONF_UNIT_OF_MEASUREMENT: PERCENTAGE,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x64,\n                },\n            },\n            0xA1: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.POWER,\n                    CONF_MAXIMUM: 0xFFFD,\n                    CONF_BYTE_LENGTH: 0x02,\n                },\n            },\n            0xE0: {\n                CONF_ICON: \"mdi:solar-power-variant-outline\",\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                CONF_ICON_POSITIVE: \"mdi:solar-power-variant\",\n                CONF_ICON_NEGATIVE: \"mdi:solar-power-variant-outline\",\n                CONF_ICON_ZERO: \"mdi:solar-power-variant-outline\",\n            },\n            0xE1: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            },\n            0xE3: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            },\n            0xE5: {\n                CONF_ICON: \"mdi:percent\",\n                CONF_UNIT_OF_MEASUREMENT: PERCENTAGE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0x64,\n                },\n            },\n            0xE6: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.POWER,\n                    CONF_MAXIMUM: 0xFFFD,\n                    CONF_BYTE_LENGTH: 0x02,\n                },\n            },\n            0xE7: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.POWER,\n                    CONF_MAXIMUM: 0xFFFD,\n                    CONF_BYTE_LENGTH: 0x02,\n                },\n            },\n            0xE8: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.POWER,\n                    CONF_MAXIMUM: 0xFFFD,\n                    CONF_BYTE_LENGTH: 0x02,\n                },\n            },\n            0xE9: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.POWER,\n                    CONF_MAXIMUM: 0xFFFD,\n                    CONF_BYTE_LENGTH: 0x02,\n                },\n            },\n        },\n        0x7B: {  # Floor heater\n            0xE0: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 16,\n                    CONF_MAXIMUM: 40,\n                    TYPE_SWITCH: {\n                        CONF_NAME: \"Auto\",\n                        CONF_SERVICE_DATA: {DATA_STATE_ON: 0x41, DATA_STATE_OFF: 16},\n                    },\n                },\n            },\n            0xE1: {\n                CONF_ICON: \"mdi:thermometer\",\n                CONF_TYPE: None,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_AS_ZERO: 0x30,\n                    CONF_MINIMUM: 0x31,\n                    CONF_MAXIMUM: 0x3F,\n                    CONF_MAX_OPC: 0xD1,\n                    TYPE_SWITCH: {\n                        CONF_NAME: \"Auto\",\n                        CONF_SERVICE_DATA: {DATA_STATE_ON: 0x41, DATA_STATE_OFF: 0x31},\n                    },\n                },\n            },\n            0xE2: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xE3: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0x90: {\n                CONF_ICON: \"mdi:timer\",\n            },\n            0x91: {\n                CONF_ICON: \"mdi:timer-outline\",\n            },\n            0x94: {\n                CONF_ICON: \"mdi:timer\",\n            },\n            0x95: {\n                CONF_ICON: \"mdi:timer-outline\",\n            },\n        },\n        0x7C: {  # Fuel cell\n            0xC2: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xC4: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xC5: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            },\n            0xCC: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xCD: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            },\n            0xC7: {\n                CONF_TYPE: SensorDeviceClass.GAS,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolumeFlowRate.CUBIC_METERS_PER_HOUR,\n            },\n            0xC8: {\n                CONF_TYPE: SensorDeviceClass.GAS,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n            },\n        },\n        0x7D: {  # Storage battery\n            0xA0: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xA1: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xA2: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xA3: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xA4: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xA5: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xA6: {\n                CONF_TYPE: SensorDeviceClass.BATTERY,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.BATTERY,\n                    CONF_MAXIMUM: 0x64,\n                },\n            },\n            0xA7: {\n                CONF_TYPE: SensorDeviceClass.BATTERY,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.BATTERY,\n                    CONF_MAXIMUM: 0x64,\n                },\n            },\n            0xA8: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            },\n            0xA9: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            },\n            0xAA: {\n                CONF_ICON: \"mdi:battery\",\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.ENERGY,\n                    CONF_MAXIMUM: 0x3B9AC9FF,\n                    CONF_BYTE_LENGTH: 0x04,\n                },\n            },\n            0xAB: {\n                CONF_ICON: \"mdi:battery\",\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.ENERGY,\n                    CONF_MAXIMUM: 0x3B9AC9FF,\n                    CONF_BYTE_LENGTH: 0x04,\n                },\n            },\n            0xD0: {\n                CONF_ICON: \"mdi:battery\",\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xD1: {\n                CONF_MULTIPLIER: 0.1,\n                CONF_UNIT_OF_MEASUREMENT: \"Ah\",\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xD2: {\n                CONF_TYPE: SensorDeviceClass.VOLTAGE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xD3: {\n                CONF_ICON_POSITIVE: \"mdi:battery-arrow-up\",\n                CONF_ICON_NEGATIVE: \"mdi:battery-arrow-down\",\n                CONF_ICON_ZERO: \"mdi:battery\",\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xD6: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            },\n            0xD8: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            },\n            0xE0: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xE2: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n            },\n            0xE4: {\n                CONF_TYPE: SensorDeviceClass.BATTERY,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xE5: {\n                CONF_TYPE: SensorDeviceClass.BATTERY,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xE7: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.ENERGY,\n                    CONF_MAXIMUM: 0x3B9AC9FF,\n                    CONF_BYTE_LENGTH: 0x04,\n                },\n            },\n            0xE8: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.ENERGY,\n                    CONF_MAXIMUM: 0x3B9AC9FF,\n                    CONF_BYTE_LENGTH: 0x04,\n                },\n            },\n            0xEB: {\n                CONF_ICON: \"mdi:battery\",\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.POWER,\n                    CONF_MAXIMUM: 0x3B9AC9FF,\n                    CONF_BYTE_LENGTH: 0x04,\n                },\n            },\n            0xEC: {\n                CONF_ICON: \"mdi:battery\",\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.POWER,\n                    CONF_MAXIMUM: 0x3B9AC9FF,\n                    CONF_BYTE_LENGTH: 0x04,\n                },\n            },\n            0xEF: {\n                CONF_TYPE: SensorDeviceClass.VOLTAGE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n        },\n        0x80: {  # Electric energy meter\n            0xE0: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n                CONF_MULTIPLIER_OPCODE: 0xE2,\n            },\n            0xE2: {\n                CONF_DISABLED_DEFAULT: True,\n            },\n        },\n        0x81: {  # Water flow meter\n            0xE0: {\n                # CONF_ICON: \"mdi:water\",\n                CONF_TYPE: SensorDeviceClass.WATER,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_MULTIPLIER_OPCODE: 0xE1,\n            },\n            0xE1: {\n                CONF_DISABLED_DEFAULT: True,\n            },\n        },\n        0x82: {  # Gas meter\n            0xE0: {\n                # CONF_ICON: \"mdi:gas-burner\",\n                CONF_TYPE: SensorDeviceClass.GAS,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_MULTIPLIER: 0.001,\n            }\n        },\n        0x87: {  # Distribution panel metering\n            0xC2: {\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xB3: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n                TYPE_DATA_ARRAY_WITH_SIZE_OPCODE: 0xB1,\n                CONF_MULTIPLIER_OPCODE: 0xC2,\n            },\n            0xB7: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_DATA_ARRAY_WITH_SIZE_OPCODE: 0xB1,\n            },\n            0xC0: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n                CONF_MULTIPLIER_OPCODE: 0xC2,\n            },\n            0xC1: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n                CONF_MULTIPLIER_OPCODE: 0xC2,\n            },\n            0xC6: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xC7: {\n                CONF_TYPE: SensorDeviceClass.CURRENT,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_DATA_DICT: [\"r_phase_amperes\", \"t_phase_amperes\"],\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xC8: {\n                CONF_TYPE: SensorDeviceClass.VOLTAGE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_DATA_DICT: [\"r_sn_voltage\", \"sn_t_voltage\"],\n                CONF_DISABLED_DEFAULT: True,\n            },\n        },\n        0x88: {  # Low voltage smart electric energy meter\n            0xD3: {\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xE0: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n                CONF_MULTIPLIER_OPCODE: 0xE1,\n                CONF_MULTIPLIER_OPTIONAL_OPCODE: 0xD3,\n            },\n            0xE1: {\n                CONF_DISABLED_DEFAULT: True,\n            },\n            0xE3: {\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n                CONF_MULTIPLIER_OPCODE: 0xE1,\n                CONF_MULTIPLIER_OPTIONAL_OPCODE: 0xD3,\n            },\n            0xE7: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n            0xE8: {\n                CONF_TYPE: SensorDeviceClass.CURRENT,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_DATA_DICT: [\"r_phase_amperes\", \"t_phase_amperes\"],\n            },\n            # 0xEA: {\n            #     TYPE_DATA_DICT: [\"time\", \"culmative_value\"],\n            # },\n            # 0xEB: {\n            #     TYPE_DATA_DICT: [\"time\", \"culmative_value\"],\n            # },\n            0xD3: {CONF_DISABLED_DEFAULT: True},\n            0xE1: {CONF_DISABLED_DEFAULT: True},\n        },\n        0xA3: {  # Lighting system\n            0xC0: {  # Set scene\n                CONF_ICON: \"mdi:palette\",\n                CONF_TYPE: DEVICE_CLASS_ECHONETLITE_LIGHT_SCENE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_MAXIMUM: 0xFD,\n                    CONF_MAX_OPC: 0xC1,\n                },\n            },\n        },\n        0xA5: {  # Multiple Input PCS\n            0xE0: {  # Measured cumulative amount of electric energy (normal direction)\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n                CONF_MULTIPLIER: 0.001,\n            },\n            0xE3: {  # Measured cumulative amount of electric energy (reverse direction)\n                CONF_TYPE: SensorDeviceClass.ENERGY,\n                CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n                CONF_MULTIPLIER: 0.001,\n            },\n            0xE7: {  # Measured instantaneous amount of electricity\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },\n        },\n        0xA6: {  # Hybrid Water Heater\n            0xE1: {  # Measured amount of hot water remaining in tank\n                CONF_TYPE: SensorDeviceClass.WATER,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n            },\n            0xE2: {  # Tank Capacity\n                CONF_TYPE: SensorDeviceClass.WATER,\n                CONF_UNIT_OF_MEASUREMENT: UnitOfVolume.LITERS,\n            },\n        },\n    },\n    0x03: {  # Cooking/housework-related device class group\n        0xB7: {  # Refrigerator\n            0xB0: {\n                CONF_ICON: \"mdi:door\",\n                CONF_ICONS: {\n                    DATA_STATE_OPEN: \"mdi:door-open\",\n                    DATA_STATE_CLOSE: \"mdi:door-closed\",\n                },\n            },  # \"Door open/close status\",\n            0xB1: {\n                CONF_ICON: \"mdi:door\",\n                CONF_ICONS: {\n                    DATA_STATE_OPEN: \"mdi:door-open\",\n                    DATA_STATE_CLOSE: \"mdi:door-closed\",\n                },\n            },  # \"Door open warning\",\n            0xB2: {\n                CONF_ICON: \"mdi:door\",\n                CONF_ICONS: {\n                    DATA_STATE_OPEN: \"mdi:door-open\",\n                    DATA_STATE_CLOSE: \"mdi:door-closed\",\n                },\n            },  # \"Refrigerator compartment door status\",\n            0xB3: {\n                CONF_ICON: \"mdi:door\",\n                CONF_ICONS: {\n                    DATA_STATE_OPEN: \"mdi:door-open\",\n                    DATA_STATE_CLOSE: \"mdi:door-closed\",\n                },\n            },  # \"Freezer compartment door status\",\n            0xB4: {\n                CONF_ICON: \"mdi:door\",\n                CONF_ICONS: {\n                    DATA_STATE_OPEN: \"mdi:door-open\",\n                    DATA_STATE_CLOSE: \"mdi:door-closed\",\n                },\n            },  # \"Ice compartment door status\",\n            0xB5: {\n                CONF_ICON: \"mdi:door\",\n                CONF_ICONS: {\n                    DATA_STATE_OPEN: \"mdi:door-open\",\n                    DATA_STATE_CLOSE: \"mdi:door-closed\",\n                },\n            },  # \"Vegetable compartment door status\",\n            0xB6: {\n                CONF_ICON: \"mdi:door\",\n                CONF_ICONS: {\n                    DATA_STATE_OPEN: \"mdi:door-open\",\n                    DATA_STATE_CLOSE: \"mdi:door-closed\",\n                },\n            },  # \"Multi-refrigera-ting mode compartment door\",\n            0xE0: {\n                TYPE_DATA_DICT: [\n                    \"refrigerator\",\n                    \"freezer\",\n                    \"ice\",\n                    \"vegetable\",\n                    \"multi_refrigerating\",\n                ],\n                CONF_DISABLED_DEFAULT: True,\n            },  # \"Maximum allowable temperature setting level\",\n            0xE2: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: -127,\n                    CONF_MAXIMUM: 126,\n                },\n            },  # \"Refrigerator compartment temperature setting\",\n            0xE3: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: -127,\n                    CONF_MAXIMUM: 126,\n                },\n            },  # \"Freezer compartment temperature setting\",\n            0xE4: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: -127,\n                    CONF_MAXIMUM: 126,\n                },\n            },  # \"Ice temperature setting\",\n            0xE5: {\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: -127,\n                    CONF_MAXIMUM: 126,\n                },\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n            },  # \"Vegetable compartment temperature setting\",\n            0xE6: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: -127,\n                    CONF_MAXIMUM: 126,\n                },\n            },  # \"Multi-refrigera-ting mode compartment temperature setting\",\n            0xE9: {\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 1,\n                    CONF_MAXIMUM: 0xFF,\n                    CONF_MAX_OPC: [0xE0, \"refrigerator\"],\n                },\n            },  # \"Refrigerator compartment temperature level setting\",\n            0xEA: {\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 1,\n                    CONF_MAXIMUM: 0xFF,\n                    CONF_MAX_OPC: [0xE0, \"freezer\"],\n                },\n            },  # \"Freezer compartment temperature level setting\",\n            0xEB: {\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 1,\n                    CONF_MAXIMUM: 0xFF,\n                    CONF_MAX_OPC: [0xE0, \"ice\"],\n                },\n            },  # \"ice compartment temperature level setting\",\n            0xEC: {\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 1,\n                    CONF_MAXIMUM: 0xFF,\n                    CONF_MAX_OPC: [0xE0, \"vegetable\"],\n                },\n            },  # \"Vegetable compartment temperature level setting\",\n            0xED: {\n                TYPE_NUMBER: {\n                    CONF_TYPE: NumberDeviceClass.TEMPERATURE,\n                    CONF_MINIMUM: 1,\n                    CONF_MAXIMUM: 0xFF,\n                    CONF_MAX_OPC: [0xE0, \"multi_refrigerating\"],\n                },\n            },  # \"Multi-refrigera-ting mode compartment temperature level setting\",\n            0xD1: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n            },  # \"Measured refrigerator compartment temperature\",\n            0xD2: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n            },  # \"Measured freezer compartment temperature\",\n            0xD3: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n            },  # \"Measured subzero-fresh compartment temperature\",\n            0xD4: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n            },  # \"Measured vegetable compartment temperature\",\n            0xD5: {\n                CONF_TYPE: SensorDeviceClass.TEMPERATURE,\n            },  # \"Measured multi-refrigeratin g mode compartment temperature\",\n            0xD8: {\n                TYPE_DATA_DICT: [\"maximum_rotation_speed\", \"rotation_speed\"]\n            },  # \"Compressor rotation speed\",\n            0xDA: {\n                CONF_TYPE: SensorDeviceClass.CURRENT,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n                CONF_MULTIPLIER: 0.1,\n            },  # \"Measured electric current consumption\",\n            0xDC: {\n                CONF_TYPE: SensorDeviceClass.POWER,\n                CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            },  # \"Rated power consumption\",\n            0xA0: {\n                CONF_ICON: \"mdi:snowflake-check\"\n            },  # \"Quick freeze function setting\",\n            0xA1: {\n                CONF_ICON: \"mdi:fridge-bottom\"\n            },  # \"Quick refrigeration function setting\",\n            0xA4: {CONF_ICON: \"mdi:dice-1-outline\"},  # \"Icemaker setting\",\n            0xA5: {CONF_ICON: \"mdi:dice-1-outline\"},  # \"Icemaker operation status\",\n            0xA6: {CONF_ICON: \"mdi:water-alert-outline\"},  # \"Icemaker tank status\",\n            0xA8: {\n                CONF_ICON: \"mdi:water-thermometer\"\n            },  # \"Refrigerator compartment humidification function setting\",\n            0xA9: {\n                CONF_ICON: \"mdi:water-thermometer\"\n            },  # \"Vegetable compartment humidification function setting\",\n            0xAD: {CONF_ICON: \"mdi:scent\"},  # \"Deodorization function setting\",\n        },  # Refrigerator\n    },\n    \"default\": {\n        CONF_ICON: None,\n        CONF_TYPE: None,\n        CONF_STATE_CLASS: None,\n    },\n}\n\nENABLE_SUPER_ENERGY_DEFAULT = {\n    # If False is not specified here, the default is True.\n    # 0x01: {\n    #     0x35: False,\n    # },\n}\n\n# Some entities that overlap with control entities are excluded from setup\nNON_SETUP_SINGLE_ENYITY = {\n    0x01: {\n        # Home Air Conditioner\n        0x30: {ENL_HVAC_MODE, ENL_HVAC_SET_TEMP, ENL_HVAC_SILENT_MODE},\n        # Ceiling fan\n        0x3A: {\n            ENL_FANSPEED_PERCENT,\n            ENL_FAN_DIRECTION,\n            ENL_FAN_OSCILLATION,\n            ENL_FAN_LIGHT_STATUS,\n            ENL_FAN_LIGHT_MODE,\n            ENL_FAN_LIGHT_BRIGHTNESS,\n            ENL_FAN_LIGHT_COLOR_TEMP,\n            ENL_FAN_LIGHT_NIGHT_BRIGHTNESS,\n            ENL_BUZZER,\n        },\n    },\n    0x02: {\n        0x60: {ENL_OPENING_LEVEL, ENL_BLIND_ANGLE, ENL_OPENCLOSE_STATUS},\n        0x61: {ENL_OPENING_LEVEL, ENL_BLIND_ANGLE, ENL_OPENCLOSE_STATUS},\n        0x62: {ENL_OPENING_LEVEL, ENL_BLIND_ANGLE, ENL_OPENCLOSE_STATUS},\n        0x63: {ENL_OPENING_LEVEL, ENL_BLIND_ANGLE, ENL_OPENCLOSE_STATUS},\n        0x64: {ENL_OPENING_LEVEL, ENL_BLIND_ANGLE, ENL_OPENCLOSE_STATUS},\n        0x65: {ENL_OPENING_LEVEL, ENL_BLIND_ANGLE, ENL_OPENCLOSE_STATUS},\n        0x66: {ENL_OPENING_LEVEL, ENL_BLIND_ANGLE, ENL_OPENCLOSE_STATUS},\n        # General Lighting\n        0x90: {ENL_BRIGHTNESS, ENL_COLOR_TEMP},\n        # Single Function Lighting\n        0x91: {ENL_BRIGHTNESS, ENL_COLOR_TEMP},\n    },\n}\n\nATTR_STATE_ON = \"on\"\nATTR_STATE_OFF = \"off\"\n\nFAN_SPEED_OPTIONS = {\n    \"auto\": \"Auto\",\n    \"minimum\": \"Minimum\",\n    \"low\": \"Low\",\n    \"medium-low\": \"Medium-Low\",\n    \"medium\": \"Medium\",\n    \"medium-high\": \"Medium-High\",\n    \"high\": \"High\",\n    \"very-high\": \"Very-High\",\n    \"max\": \"Max\",\n}\n\nAIRFLOW_HORIZ_OPTIONS = {\n    \"rc-right\": \"Right Center + Right\",\n    \"left-lc\": \"Left + Left Center\",\n    \"lc-center-rc\": \"Left + Center + Right Center\",\n    \"left-lc-rc-right\": \"Left + Left Center + Right Center + Right\",\n    \"right\": \"Right\",\n    \"rc\": \"Right Center\",\n    \"center\": \"Center\",\n    \"center-right\": \"Center + Right\",\n    \"center-rc\": \"Center + Right Center\",\n    \"center-rc-right\": \"Center + Right Center + Right\",\n    \"lc\": \"Left Center\",\n    \"lc-right\": \"Left Center + Right\",\n    \"lc-rc\": \"Left Center + Right Center\",\n    \"lc-rc-right\": \"Left Center + Right Center + Right\",\n    \"lc-center\": \"Left Center + Center\",\n    \"lc-center-right\": \"Left Center + Center + Right\",\n    \"lc-center-rc-right\": \"Left Center + Center + Right Center + Right\",\n    \"left\": \"Left\",\n    \"left-right\": \"Left + Right\",\n    \"left-rc\": \"Left + Right Center\",\n    \"left-rc-right\": \"Left + Right Center + Right\",\n    \"left-center\": \"Left + Center\",\n    \"left-center-right\": \"Left + Center + Right\",\n    \"left-center-rc\": \"Left + Center + Right Center\",\n    \"left-center-rc-right\": \"Left + Center + Right Center + Right\",\n    \"left-lc-right\": \"Left + Left Center + Right\",\n    \"left-lc-rc\": \"Left + Left Center + Right Center\",\n    \"left-lc-center\": \"Left + Left Center + Center\",\n    \"left-lc-center-right\": \"Left + Left Center + Center + Right\",\n    \"left-lc-center-rc\": \"Left + Left Center + Center + Right Center\",\n    \"left-lc-center-rc-right\": \"Left + Left Center + Center + Right Center + Right\",\n}\n\nAIRFLOW_VERT_OPTIONS = {\n    \"upper\": \"Upper\",\n    \"upper-central\": \"Upper Central\",\n    \"central\": \"Central\",\n    \"lower-central\": \"Lower Central\",\n    \"lower\": \"Lower\",\n}\n\nAUTO_DIRECTION_OPTIONS = {\n    \"auto\": \"Auto\",\n    \"non-auto\": \"Non-Auto\",\n    \"auto-vert\": \"Auto-vert\",\n    \"auto-horiz\": \"Auto-horiz\",\n}\n\nSWING_MODE_OPTIONS = {\n    \"not-used\": \"Not Used (Off)\",\n    \"vert\": \"Vertical\",\n    \"horiz\": \"Horizontal\",\n    \"vert-horiz\": \"Vertical-Horizontal\",\n}\n\nSILENT_MODE_OPTIONS = {\n    \"normal\": \"Normal\",\n    \"high-speed\": \"High Speed\",\n    \"silent\": \"Silent\",\n}\n\nHVAC_MODE_OPTIONS = {\"as_off\": \"As Off\", \"as_idle\": \"As Idle\"}\n\nOPTION_HA_UI_SWING = \"ha_ui_swing\"\n\nUSER_OPTIONS = {\n    ENL_FANSPEED: {\"option\": \"fan_settings\", \"option_list\": FAN_SPEED_OPTIONS},\n    ENL_SWING_MODE: {\"option\": \"swing_mode\", \"option_list\": SWING_MODE_OPTIONS},\n    ENL_AUTO_DIRECTION: {\n        \"option\": \"auto_direction\",\n        \"option_list\": AUTO_DIRECTION_OPTIONS,\n    },\n    ENL_AIR_VERT: {\"option\": \"swing_vert\", \"option_list\": AIRFLOW_VERT_OPTIONS},\n    ENL_AIR_HORZ: {\"option\": \"swing_horiz\", \"option_list\": AIRFLOW_HORIZ_OPTIONS},\n    ENL_HVAC_MODE: {\n        \"option\": CONF_OTHER_MODE,\n        \"option_list\": [\n            {\"value\": \"as_off\", \"label\": \"As Off\"},\n            {\"value\": \"as_idle\", \"label\": \"As Idle\"},\n        ],\n    },\n    OPTION_HA_UI_SWING: {\"option\": OPTION_HA_UI_SWING, \"option_list\": []},\n}\n\nTEMP_OPTIONS = {\n    \"min_temp_heat\": {\"min\": 10, \"max\": 25, \"default\": 16},\n    \"max_temp_heat\": {\"min\": 18, \"max\": 30, \"default\": 30},\n    \"min_temp_cool\": {\"min\": 15, \"max\": 25, \"default\": 16},\n    \"max_temp_cool\": {\"min\": 18, \"max\": 30, \"default\": 30},\n    \"min_temp_auto\": {\"min\": 15, \"max\": 25, \"default\": 16},\n    \"max_temp_auto\": {\"min\": 18, \"max\": 30, \"default\": 30},\n}\n\nMISC_OPTIONS = {\n    CONF_FORCE_POLLING: {\"type\": bool, \"default\": False},\n    CONF_ENABLE_SUPER_ENERGY: {\n        \"type\": bool,\n        \"default\": [ENABLE_SUPER_ENERGY_DEFAULT, True],\n    },\n    CONF_BATCH_SIZE_MAX: {\"type\": int, \"default\": 10, \"min\": 1, \"max\": 30},\n}\n"
  },
  {
    "path": "custom_components/echonetlite/cover.py",
    "content": "import logging\nimport math\n\nfrom typing import Any\n\nfrom pychonet.lib.epc_functions import (\n    DATA_STATE_CLOSE,\n    DATA_STATE_OPEN,\n    DATA_STATE_STOP,\n    DATA_STATE_OPENING,\n    DATA_STATE_CLOSING,\n)\nfrom . import get_device_name\nfrom .const import DOMAIN\n\nfrom homeassistant.components.cover import (\n    ATTR_POSITION,\n    ATTR_TILT_POSITION,\n    CoverEntity,\n    CoverEntityFeature,\n)\n\nfrom pychonet.lib.eojx import EOJX_CLASS\nfrom pychonet.ElectricBlind import (\n    ENL_BLIND_ANGLE,\n    ENL_OPENCLOSE_STATUS,\n    ENL_OPENING_LEVEL,\n    ENL_OPENSTATE,\n)\n\nfrom homeassistant.util.percentage import (\n    percentage_to_ranged_value,\n    ranged_value_to_percentage,\n)\n\nTILT_RANGE = (1, 180)\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_setup_entry(hass, config_entry, async_add_devices):\n    \"\"\"Set up entry.\"\"\"\n    entities = []\n    for entity in hass.data[DOMAIN][config_entry.entry_id]:\n        if entity[\"instance\"][\"eojgc\"] == 0x02 and entity[\"instance\"][\"eojcc\"] in (\n            0x60,\n            0x61,\n            0x62,\n            0x63,\n            0x64,\n            0x65,\n            0x66,\n        ):\n            # 0x60: \"Electrically operated blind/shade\"\n            # 0x61: \"Electrically operated shutter\"\n            # 0x62: \"Electrically operated curtain\"\n            # 0x63: \"Electrically operated rain sliding door/shutter\"\n            # 0x64: \"Electrically operated gate\"\n            # 0x65: \"Electrically operated window\"\n            # 0x66: \"Automatically operated entrance door/sliding door\"\n            entities.append(EchonetCover(entity[\"echonetlite\"], config_entry))\n    async_add_devices(entities, True)\n\n\nclass EchonetCover(CoverEntity):\n    \"\"\"Representation of an ECHONETLite climate device.\"\"\"\n\n    def __init__(self, connector, config):\n        \"\"\"Initialize the cover device.\"\"\"\n        name = get_device_name(connector, config)\n        self._attr_name = name\n        self._device_name = name\n        self._connector = connector  # new line\n        self._attr_unique_id = (\n            self._connector._uidi if self._connector._uidi else self._connector._uid\n        )\n        self._attr_is_closed = False\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._olddata = {}\n        self._attr_should_poll = True\n        self._support_flags = (\n            CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP\n        )\n        if ENL_OPENING_LEVEL in list(self._connector._setPropertyMap):\n            self._support_flags |= CoverEntityFeature.SET_POSITION\n\n        if ENL_BLIND_ANGLE in list(self._connector._setPropertyMap):\n            self._support_flags |= (\n                CoverEntityFeature.OPEN_TILT\n                | CoverEntityFeature.CLOSE_TILT\n                # not supported individually (just global STOP)\n                # | CoverEntityFeature.STOP_TILT\n                | CoverEntityFeature.SET_TILT_POSITION\n            )\n\n        self._attr_current_cover_position = None\n        self._attr_current_cover_tilt_position = None\n        self._attr_is_opening = False\n        self._attr_is_closing = False\n        self.update_attr()\n        self.update_option_listener()\n\n    async def async_close_cover(self, **kwargs: Any) -> None:\n        await self._connector._instance.setMessage(ENL_OPENSTATE, 0x42)\n        self._connector._update_data[ENL_OPENSTATE] = DATA_STATE_CLOSE\n        if ENL_OPENCLOSE_STATUS in self._connector._update_data:\n            self._attr_is_opening = False\n            self._attr_is_closing = True\n\n    async def async_open_cover(self, **kwargs: Any) -> None:\n        await self._connector._instance.setMessage(ENL_OPENSTATE, 0x41)\n        self._connector._update_data[ENL_OPENSTATE] = DATA_STATE_OPEN\n        if ENL_OPENCLOSE_STATUS in self._connector._update_data:\n            self._attr_is_opening = True\n            self._attr_is_closing = False\n\n    async def async_stop_cover(self, **kwargs: Any) -> None:\n        await self._connector._instance.setMessage(ENL_OPENSTATE, 0x43)\n        self._connector._update_data[ENL_OPENSTATE] = DATA_STATE_STOP\n        self._attr_is_opening = False\n        self._attr_is_closing = False\n        await self._connector.async_update()\n        self.update_attr()\n\n    async def async_set_cover_position(self, **kwargs: Any) -> None:\n        desired_position = kwargs[ATTR_POSITION]\n        current_position = self._attr_current_cover_position\n        await self._connector._instance.setMessage(ENL_OPENING_LEVEL, desired_position)\n        self._connector._update_data[ENL_OPENING_LEVEL] = int(desired_position)\n        self._attr_is_opening = desired_position > current_position\n        self._attr_is_closing = desired_position < current_position\n\n    async def async_close_cover_tilt(self, **kwargs: Any) -> None:\n        await self._connector._instance.setMessage(ENL_BLIND_ANGLE, 0)\n        self._connector._update_data[ENL_BLIND_ANGLE] = 0\n\n    async def async_open_cover_tilt(self, **kwargs: Any) -> None:\n        await self._connector._instance.setMessage(ENL_BLIND_ANGLE, 180)\n        self._connector._update_data[ENL_BLIND_ANGLE] = 180\n\n    async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:\n        tilt = math.ceil(\n            percentage_to_ranged_value(TILT_RANGE, kwargs[ATTR_TILT_POSITION])\n        )\n        await self._connector._instance.setMessage(ENL_BLIND_ANGLE, tilt)\n        self._connector._update_data[ENL_BLIND_ANGLE] = int(tilt)\n\n    async def async_update(self):\n        await self._connector.async_update()\n\n    def update_attr(self):\n        if (\n            ENL_OPENING_LEVEL in self._connector._update_data\n            and self._connector._update_data[ENL_OPENING_LEVEL] != None\n        ):\n            self._attr_current_cover_position = int(\n                self._connector._update_data[ENL_OPENING_LEVEL]\n            )\n            self._attr_is_closed = self._attr_current_cover_position == 0\n        else:\n            self._attr_is_closed = (\n                self._connector._update_data[ENL_OPENSTATE] == DATA_STATE_CLOSE\n            )\n        if ENL_OPENCLOSE_STATUS in self._connector._update_data:\n            self._attr_is_opening = (\n                self._connector._update_data[ENL_OPENCLOSE_STATUS] == DATA_STATE_OPENING\n            )\n            self._attr_is_closing = (\n                self._connector._update_data[ENL_OPENCLOSE_STATUS] == DATA_STATE_CLOSING\n            )\n        if (\n            ENL_BLIND_ANGLE in self._connector._update_data\n            and self._connector._update_data[ENL_BLIND_ANGLE] != None\n        ):\n            self._attr_current_cover_tilt_position = ranged_value_to_percentage(\n                TILT_RANGE, int(self._connector._update_data[ENL_BLIND_ANGLE])\n            )\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._instance._eojgc,\n                    self._connector._instance._eojcc,\n                    self._connector._instance._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._connector._instance._eojgc][\n                self._connector._instance._eojcc\n            ],\n            # \"sw_version\": \"\",\n        }\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush=False):\n        changed = (\n            self._olddata != self._connector._update_data\n        ) or self._attr_available != self._server_state[\"available\"]\n        if changed:\n            self._olddata = self._connector._update_data.copy()\n            self.update_attr()\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self.async_schedule_update_ha_state()\n\n    def update_option_listener(self):\n        _LOGGER.info(f\"{self._device_name}: _should_poll is {self._attr_should_poll}\")\n"
  },
  {
    "path": "custom_components/echonetlite/fan.py",
    "content": "import logging\nfrom pychonet.HomeAirCleaner import FAN_SPEED\nfrom pychonet.lib.const import ENL_STATUS\n\nfrom pychonet.lib.eojx import EOJX_CLASS\nfrom pychonet.CeilingFan import (\n    ENL_FANSPEED_PERCENT,\n    ENL_FAN_DIRECTION,\n    ENL_FAN_OSCILLATION,\n)\nfrom homeassistant.components.fan import FanEntity, FanEntityFeature\nfrom homeassistant.const import (\n    PRECISION_WHOLE,\n)\nfrom . import get_device_name\nfrom .const import (\n    CONF_FORCE_POLLING,\n    DATA_STATE_ON,\n    DOMAIN,\n    ENL_FANSPEED,\n)\n\n_LOGGER = logging.getLogger(__name__)\n\nDEFAULT_FAN_MODES = list(\n    FAN_SPEED.keys()\n)  # [\"auto\",\"minimum\",\"low\",\"medium-low\",\"medium\",\"medium-high\",\"high\",\"very-high\",\"max\"]\n\n\nasync def async_setup_entry(hass, config_entry, async_add_devices):\n    \"\"\"Set up entry.\"\"\"\n    entities = []\n    for entity in hass.data[DOMAIN][config_entry.entry_id]:\n        if entity[\"instance\"][\"eojgc\"] == 0x01 and (\n            entity[\"instance\"][\"eojcc\"] == 0x35 or entity[\"instance\"][\"eojcc\"] == 0x3A\n        ):  # Home Air Cleaner or Celing Fan\n            entities.append(EchonetFan(entity[\"echonetlite\"], config_entry))\n    async_add_devices(entities, True)\n\n\nclass EchonetFan(FanEntity):\n    \"\"\"Representation of an ECHONETLite Fan device (eg Air purifier).\"\"\"\n\n    def __init__(self, connector, config):\n        \"\"\"Initialize the climate device.\"\"\"\n        name = get_device_name(connector, config)\n        self._attr_name = name\n        self._device_name = name\n        self._connector = connector  # new line\n        self._attr_unique_id = (\n            self._connector._uidi if self._connector._uidi else self._connector._uid\n        )\n        self._precision = 1.0\n        self._target_temperature_step = 1\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._attr_supported_features = FanEntityFeature(0)\n        if hasattr(FanEntityFeature, \"TURN_ON\"):  # v2024.8\n            self._attr_supported_features |= FanEntityFeature.TURN_ON\n        if hasattr(FanEntityFeature, \"TURN_OFF\"):\n            self._attr_supported_features |= FanEntityFeature.TURN_OFF\n        if ENL_FANSPEED in list(self._connector._setPropertyMap):\n            self._attr_supported_features |= FanEntityFeature.PRESET_MODE\n        if ENL_FANSPEED_PERCENT in list(self._connector._setPropertyMap):\n            self._attr_supported_features |= FanEntityFeature.SET_SPEED\n        if ENL_FAN_DIRECTION in list(self._connector._setPropertyMap):\n            self._attr_supported_features |= FanEntityFeature.DIRECTION\n        if ENL_FAN_OSCILLATION in list(self._connector._setPropertyMap):\n            self._attr_supported_features |= FanEntityFeature.OSCILLATE\n        self._olddata = {}\n\n        self._attr_should_poll = True\n        self._attr_available = True\n\n        self._attr_speed_count = getattr(self._connector._instance, \"SPEED_COUNT\", 100)\n\n        self._set_attrs()\n        self.update_option_listener()\n\n        # see, https://developers.home-assistant.io/blog/2024/07/19/fan-fanentityfeatures-turn-on_off/\n        self._enable_turn_on_off_backwards_compatibility = False\n\n    async def async_update(self):\n        try:\n            await self._connector.async_update()\n        except TimeoutError:\n            pass\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._instance._eojgc,\n                    self._connector._instance._eojcc,\n                    self._connector._instance._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._connector._instance._eojgc][\n                self._connector._instance._eojcc\n            ],\n            # \"sw_version\": \"\",\n        }\n\n    @property\n    def precision(self) -> float:\n        return PRECISION_WHOLE\n\n    @property\n    def is_on(self):\n        \"\"\"Return true if the device is on.\"\"\"\n        return (\n            True if self._connector._update_data[ENL_STATUS] == DATA_STATE_ON else False\n        )\n\n    def _set_attrs(self):\n        # @property\n        # def preset_mode(self):\n        \"\"\"Return the fan setting.\"\"\"\n        self._attr_preset_mode = (\n            self._connector._update_data[ENL_FANSPEED]\n            if ENL_FANSPEED in self._connector._update_data\n            else None\n        )\n\n        # @property\n        # def percentage(self):\n        \"\"\"Return the fan setting.\"\"\"\n        self._attr_percentage = (\n            self._connector._update_data[ENL_FANSPEED_PERCENT]\n            if ENL_FANSPEED_PERCENT in self._connector._update_data\n            else None\n        )\n\n        # @property\n        # def current_direction(self):\n        \"\"\"Return the fan direction.\"\"\"\n        self._attr_current_direction = (\n            self._connector._update_data[ENL_FAN_DIRECTION]\n            if ENL_FAN_DIRECTION in self._connector._update_data\n            else None\n        )\n\n        # @property\n        # def oscillating(self):\n        \"\"\"Return the fan oscillating.\"\"\"\n        self._attr_oscillating = (\n            self._connector._update_data[ENL_FAN_OSCILLATION]\n            if ENL_FAN_OSCILLATION in self._connector._update_data\n            else None\n        )\n\n        # @property\n        # def preset_modes(self):\n        \"\"\"Return the list of available fan modes.\"\"\"\n        if (\n            ENL_FANSPEED in list(self._connector._user_options.keys())\n            and self._connector._user_options[ENL_FANSPEED] is not False\n        ):\n            self._attr_preset_modes = self._connector._user_options[ENL_FANSPEED]\n        else:\n            self._attr_preset_modes = DEFAULT_FAN_MODES\n\n    async def async_set_direction(self, direction: str) -> None:\n        await self._connector._instance.setFanDirection(direction)\n\n    async def async_turn_on(\n        self,\n        percentage: int | None = None,\n        preset_mode: str | None = None,\n        **kwargs,\n    ) -> None:\n        \"\"\"Turn on.\"\"\"\n        await self._connector._instance.on()\n\n    async def async_turn_off(self, **kwargs):\n        \"\"\"Turn off.\"\"\"\n        await self._connector._instance.off()\n\n    async def async_oscillate(self, oscillating: bool) -> None:\n        await self._connector._instance.setFanOscillation(oscillating)\n\n    async def async_set_percentage(self, percentage: int) -> None:\n        \"\"\"Set the speed percentage of the fan.\"\"\"\n        await self._connector._instance.setFanSpeedPercent(percentage)\n\n    async def async_set_preset_mode(self, preset_mode: str):\n        \"\"\"Set new fan mode.\"\"\"\n        await self._connector._instance.setFanSpeed(preset_mode)\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n        self._connector.add_update_option_listener(self.update_option_listener)\n\n    async def async_update_callback(self, isPush: bool = False):\n        changed = (\n            self._olddata != self._connector._update_data\n            or self._attr_available != self._server_state[\"available\"]\n        )\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._olddata = self._connector._update_data.copy()\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self._set_attrs()\n            self.async_schedule_update_ha_state(_force | isPush)\n\n    def update_option_listener(self):\n        _should_poll = (\n            ENL_STATUS not in self._connector._ntfPropertyMap\n            or (\n                FanEntityFeature.PRESET_MODE in self._attr_supported_features\n                and ENL_FANSPEED not in self._connector._ntfPropertyMap\n            )\n            or (\n                FanEntityFeature.SET_SPEED in self._attr_supported_features\n                and ENL_FANSPEED_PERCENT not in self._connector._ntfPropertyMap\n            )\n            or (\n                FanEntityFeature.DIRECTION in self._attr_supported_features\n                and ENL_FAN_DIRECTION not in self._connector._ntfPropertyMap\n            )\n            or (\n                FanEntityFeature.OSCILLATE in self._attr_supported_features\n                and ENL_FAN_OSCILLATION not in self._connector._ntfPropertyMap\n            )\n        )\n        self._attr_should_poll = (\n            self._connector._user_options.get(CONF_FORCE_POLLING, False) or _should_poll\n        )\n        self._attr_extra_state_attributes = {\"notify\": \"No\" if _should_poll else \"Yes\"}\n        _LOGGER.debug(f\"{self._attr_name}: _should_poll is {_should_poll}\")\n"
  },
  {
    "path": "custom_components/echonetlite/light.py",
    "content": "import logging\n\nfrom pychonet.GeneralLighting import ENL_STATUS, ENL_BRIGHTNESS, ENL_COLOR_TEMP\nfrom pychonet.CeilingFan import (\n    ENL_FAN_LIGHT_STATUS,\n    ENL_FAN_LIGHT_BRIGHTNESS,\n    ENL_FAN_LIGHT_COLOR_TEMP,\n)\n\n\nfrom pychonet.lib.const import ENL_ON\nfrom pychonet.lib.eojx import EOJX_CLASS\nfrom pychonet.lib.epc_functions import _swap_dict\n\nfrom homeassistant.components.light import (\n    ATTR_EFFECT,\n    LightEntity,\n    ColorMode,\n    LightEntityFeature,\n)\nfrom homeassistant.components.light import (\n    ATTR_BRIGHTNESS,\n    ATTR_COLOR_TEMP_KELVIN,\n)\n\nfrom . import get_device_name\nfrom .const import DATA_STATE_ON, DOMAIN, CONF_FORCE_POLLING\n\n_LOGGER = logging.getLogger(__name__)\n\nDEFAULT_BRIGHTNESS_SCALE = 255\nMIN_MIREDS = 153  # 6500k\nMAX_MIREDS = 500  # 2000k\nDEVICE_SCALE = 100\n\n\ndef _mireds_to_kelvin(mireds):\n    \"\"\"Convert mireds to kelvin.\"\"\"\n    return round(1000000 / mireds) if mireds else None\n\n\ndef _kelvin_to_mireds(kelvin):\n    \"\"\"Convert kelvin to mireds.\"\"\"\n    return round(1000000 / kelvin) if kelvin else None\n\n\nasync def async_setup_entry(hass, config_entry, async_add_devices):\n    \"\"\"Set up entry.\"\"\"\n    entities = []\n    for entity in hass.data[DOMAIN][config_entry.entry_id]:\n        eojgc = entity[\"instance\"][\"eojgc\"]\n        eojcc = entity[\"instance\"][\"eojcc\"]\n        if (eojgc == 0x02 and eojcc in (0x90, 0x91, 0xA3)) or (\n            eojgc == 0x01\n            and eojcc == 0x3A\n            and ENL_FAN_LIGHT_STATUS in entity[\"echonetlite\"]._setPropertyMap\n        ):\n            custom_options = {}\n            # General Lighting (0x90), Mono Functional Lighting (0x91), Lighting System (0xA3)\n            if eojgc == 0x02 and eojcc in (0x90, 0x91, 0xA3):\n                custom_options = {\n                    ENL_STATUS: ENL_STATUS,\n                    ENL_BRIGHTNESS: ENL_BRIGHTNESS,\n                    ENL_COLOR_TEMP: ENL_COLOR_TEMP,\n                    \"echonet_color\": {\n                        0x44: \"daylight_color\",\n                        0x43: \"daylight_white\",\n                        0x42: \"white\",\n                        0x40: \"other\",\n                        0x41: \"incandescent_lamp_color\",\n                    },\n                    \"echonet_mireds_int\": {\n                        0x44: 153,  # 6500K\n                        0x43: 200,  # 5000K\n                        0x42: 238,  # 4200K\n                        0x40: 285,  # 3500K\n                        0x41: 370,  # 2700K\n                    },  # coolest to warmest value is mired\n                    \"on\": \"on\",\n                    \"off\": \"off\",\n                }\n                custom_options[\"echonet_int_color\"] = _swap_dict(\n                    custom_options[\"echonet_color\"]\n                )\n            # Ceiling Fan (0x01-0x3A)\n            elif eojgc == 0x01 and eojcc == 0x3A:\n                custom_options = {\n                    ENL_STATUS: ENL_FAN_LIGHT_STATUS,\n                    ENL_BRIGHTNESS: ENL_FAN_LIGHT_BRIGHTNESS,\n                    ENL_COLOR_TEMP: ENL_FAN_LIGHT_COLOR_TEMP,\n                    \"echonet_color\": None,\n                    \"echonet_mireds_int\": None,\n                    \"on\": \"light_on\",\n                    \"off\": \"light_off\",\n                }\n            _LOGGER.debug(\"Configuring ECHONETlite Light entity\")\n            entities.append(\n                EchonetLight(\n                    entity[\"echonetlite\"],\n                    config_entry,\n                    custom_options,\n                )\n            )\n    _LOGGER.debug(f\"Number of light devices to be added: {len(entities)}\")\n    async_add_devices(entities, True)\n\n\nclass EchonetLight(LightEntity):\n    \"\"\"Representation of a ECHONET light device.\"\"\"\n\n    def __init__(self, connector, config, custom_options):\n        \"\"\"Initialize the climate device.\"\"\"\n        name = get_device_name(connector, config)\n        self._attr_name = name\n        self._connector = connector  # new line\n        self._attr_unique_id = (\n            self._connector._uidi if self._connector._uidi else self._connector._uid\n        )\n        self._attr_supported_features = LightEntityFeature(0)\n        self._attr_supported_color_modes = set()\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        if mireds_int := custom_options.get(\"echonet_mireds_int\"):\n            mireds = mireds_int.values()\n            self._attr_min_color_temp_kelvin = _mireds_to_kelvin(max(mireds))\n            self._attr_max_color_temp_kelvin = _mireds_to_kelvin(min(mireds))\n        else:\n            self._attr_min_color_temp_kelvin = _mireds_to_kelvin(MAX_MIREDS)\n            self._attr_max_color_temp_kelvin = _mireds_to_kelvin(MIN_MIREDS)\n        # Keep mired limits for internal calculations\n        if mireds_int := custom_options.get(\"echonet_mireds_int\"):\n            mireds = mireds_int.values()\n            self._min_mireds = min(mireds)\n            self._max_mireds = max(mireds)\n        else:\n            self._min_mireds = MIN_MIREDS\n            self._max_mireds = MAX_MIREDS\n        self._custom_options = custom_options\n        if custom_options[ENL_COLOR_TEMP] in list(self._connector._setPropertyMap):\n            self._attr_supported_color_modes.add(ColorMode.COLOR_TEMP)\n            self._attr_color_mode = ColorMode.COLOR_TEMP\n        if custom_options[ENL_BRIGHTNESS] in list(self._connector._setPropertyMap):\n            if not self._attr_supported_color_modes:\n                self._attr_supported_color_modes.add(ColorMode.BRIGHTNESS)\n                self._attr_color_mode = ColorMode.BRIGHTNESS\n        if not self._attr_supported_color_modes:\n            self._attr_supported_color_modes.add(ColorMode.ONOFF)\n            self._attr_color_mode = ColorMode.ONOFF\n\n        self._olddata = {}\n        self._attr_is_on = (\n            True\n            if self._connector._update_data[custom_options[ENL_STATUS]] == DATA_STATE_ON\n            else False\n        )\n\n        if hasattr(self._connector._instance, \"getEffectList\"):\n            self._attr_effect_list = self._connector._instance.getEffectList()\n            if self._attr_effect_list:\n                self._attr_supported_features |= LightEntityFeature.EFFECT\n\n        if hasattr(self._connector._instance, \"getLightColorLevelMax\"):\n            self._light_color_level_max = (\n                self._connector._instance.getLightColorLevelMax()\n            )\n        else:\n            self._light_color_level_max = 100\n\n        self._attr_should_poll = True\n        self._attr_available = True\n\n        self._set_attrs()\n\n        self.update_option_listener()\n\n    async def async_update(self):\n        \"\"\"Get the latest state from the Light.\"\"\"\n        try:\n            await self._connector.async_update()\n        except TimeoutError:\n            pass\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._instance._eojgc,\n                    self._connector._instance._eojcc,\n                    self._connector._instance._eojci,\n                )\n            },\n            \"name\": self._attr_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._connector._instance._eojgc][\n                self._connector._instance._eojcc\n            ],\n            # \"sw_version\": \"\",\n        }\n\n    async def async_turn_on(self, **kwargs):\n        states = {\"status\": ENL_ON}\n\n        if (\n            ATTR_BRIGHTNESS in kwargs\n            and self._attr_supported_color_modes\n            and self._attr_color_mode in {ColorMode.BRIGHTNESS, ColorMode.COLOR_TEMP}\n        ):\n            normalized_brightness = (\n                float(kwargs[ATTR_BRIGHTNESS]) / DEFAULT_BRIGHTNESS_SCALE\n            )\n            device_brightness = round(normalized_brightness * DEVICE_SCALE)\n            # Make sure the brightness is not rounded down to 0\n            device_brightness = max(device_brightness, 1)\n\n            # send the message to the lamp\n            states[\"brightness\"] = device_brightness\n            self._attr_brightness = kwargs[ATTR_BRIGHTNESS]\n\n        if (\n            ATTR_COLOR_TEMP_KELVIN in kwargs\n            and self._attr_supported_color_modes\n            and self._attr_color_mode == ColorMode.COLOR_TEMP\n        ):\n            # Convert kelvin from HA to mireds for internal device logic\n            attr_color_tmp = float(_kelvin_to_mireds(kwargs[ATTR_COLOR_TEMP_KELVIN]))\n            if self._custom_options[\"echonet_color\"]:\n                color_temp_int = 0x41\n                for i, mired in self._custom_options[\"echonet_mireds_int\"].items():\n                    if attr_color_tmp <= mired + 15:\n                        color_temp_int = i\n                        break\n                color_temp = self._custom_options[\"echonet_color\"].get(color_temp_int)\n                _LOGGER.debug(\n                    f\"New color temp of light: {color_temp} - {color_temp_int}\"\n                )\n                self._attr_color_temp_kelvin = _mireds_to_kelvin(\n                    int(self._custom_options[\"echonet_mireds_int\"].get(color_temp_int))\n                )\n            else:\n                color_scale = (attr_color_tmp - float(self._min_mireds)) / float(\n                    self._max_mireds - self._min_mireds\n                )\n                _LOGGER.debug(f\"Set color to : {color_scale}\")\n                color_temp_int = min(\n                    self._light_color_level_max,\n                    max(1, (1 - color_scale) * self._light_color_level_max),\n                )\n                _LOGGER.debug(\n                    f\"New color temp of light: {attr_color_tmp} mireds - {color_temp_int}\"\n                )\n                self._attr_color_temp_kelvin = _mireds_to_kelvin(int(attr_color_tmp))\n\n            states[\"color_temperature\"] = int(color_temp_int)\n\n        if ATTR_EFFECT in kwargs and kwargs[ATTR_EFFECT] in self._attr_effect_list:\n            states[ATTR_EFFECT] = kwargs[ATTR_EFFECT]\n\n        if hasattr(self._connector._instance, \"setLightStates\"):\n            return await self._connector._instance.setLightStates(states)\n        else:\n            \"\"\"Turn on.\"\"\"\n            result = await getattr(\n                self._connector._instance, self._custom_options[\"on\"]\n            )()\n\n            if result:\n                if states.get(\"brightness\"):\n                    result &= await self._connector._instance.setBrightness(\n                        states[\"brightness\"]\n                    )\n\n                if states.get(\"color_temperature\"):\n                    result &= await self._connector._instance.setColorTemperature(\n                        states[\"color_temperature\"]\n                    )\n\n    async def async_turn_off(self, **kwargs):\n        \"\"\"Turn off.\"\"\"\n        await getattr(self._connector._instance, self._custom_options[\"off\"])()\n\n    def _set_attrs(self):\n        if self._attr_supported_color_modes and self._attr_color_mode in {\n            ColorMode.BRIGHTNESS,\n            ColorMode.COLOR_TEMP,\n        }:\n            \"\"\"brightness of this light between 0..255.\"\"\"\n            _LOGGER.debug(\n                f\"Current brightness of light: {self._connector._update_data[self._custom_options[ENL_BRIGHTNESS]]}\"\n            )\n            brightness = (\n                int(self._connector._update_data[self._custom_options[ENL_BRIGHTNESS]])\n                if self._custom_options[ENL_BRIGHTNESS] in self._connector._update_data\n                else -1\n            )\n            if brightness >= 0:\n                self._attr_brightness = min(\n                    round(float(brightness) / DEVICE_SCALE * DEFAULT_BRIGHTNESS_SCALE),\n                    255,\n                )\n            else:\n                self._attr_brightness = 128\n\n        if (\n            self._attr_supported_color_modes\n            and self._attr_color_mode == ColorMode.COLOR_TEMP\n        ):\n            \"\"\"color temperature in kelvin.\"\"\"\n            enl_color_temp = self._custom_options[ENL_COLOR_TEMP]\n            _LOGGER.debug(\n                f\"Current color temp of light: {self._connector._update_data[enl_color_temp]}\"\n            )\n\n            if self._custom_options[\"echonet_color\"]:\n                # get the current echonet mireds and convert to kelvin\n                color_temp = (\n                    self._connector._update_data[enl_color_temp]\n                    if enl_color_temp in self._connector._update_data\n                    else \"white\"\n                )\n                mired_val = self._custom_options[\"echonet_mireds_int\"].get(\n                    self._custom_options[\"echonet_int_color\"].get(color_temp), 153\n                )\n                self._attr_color_temp_kelvin = _mireds_to_kelvin(mired_val)\n            else:\n                mired_val = (self._max_mireds - self._min_mireds) * (\n                    (\n                        self._light_color_level_max\n                        - self._connector._update_data[enl_color_temp]\n                    )\n                    / self._light_color_level_max\n                ) + self._min_mireds\n                self._attr_color_temp_kelvin = _mireds_to_kelvin(mired_val)\n\n        if hasattr(self._connector._instance, \"getEffect\"):\n            self._attr_effect = self._connector._instance.getEffect()\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush: bool = False):\n        changed = (\n            self._olddata != self._connector._update_data\n            or self._attr_available != self._server_state[\"available\"]\n        )\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._olddata = self._connector._update_data.copy()\n            self._attr_is_on = (\n                True\n                if self._connector._update_data[self._custom_options[ENL_STATUS]]\n                == DATA_STATE_ON\n                else False\n            )\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self._set_attrs()\n            self.async_schedule_update_ha_state(_force)\n            if isPush and self._attr_should_poll:\n                try:\n                    await self._connector.async_update()\n                except TimeoutError:\n                    pass\n\n    def update_option_listener(self):\n        _should_poll = (\n            self._custom_options[ENL_STATUS] not in self._connector._ntfPropertyMap\n            or (\n                self._attr_supported_color_modes\n                and ColorMode.BRIGHTNESS in self._attr_supported_color_modes\n                and self._custom_options[ENL_BRIGHTNESS]\n                not in self._connector._ntfPropertyMap\n            )\n            or (\n                self._attr_supported_color_modes\n                and ColorMode.COLOR_TEMP in self._attr_supported_color_modes\n                and ENL_COLOR_TEMP not in self._connector._ntfPropertyMap\n            )\n        )\n        self._attr_should_poll = bool(\n            self._connector._user_options.get(CONF_FORCE_POLLING, False) or _should_poll\n        )\n        self._attr_extra_state_attributes = {\"notify\": \"No\" if _should_poll else \"Yes\"}\n        _LOGGER.debug(f\"{self._attr_name}: _should_poll is {_should_poll}\")\n"
  },
  {
    "path": "custom_components/echonetlite/manifest.json",
    "content": "{\n  \"domain\": \"echonetlite\",\n  \"name\": \"ECHONET Lite\",\n  \"issue_tracker\": \"https://github.com/scottyphillips/echonetlite_homeassistant/issues\",\n  \"config_flow\": true,\n  \"documentation\": \"https://www.github.com/scottyphillips/echonetlite_homeassistant\",\n  \"requirements\": [\n    \"pychonet==2.6.18\"\n  ],\n  \"dependencies\": [],\n  \"codeowners\": [\n    \"@scottyphillips\",\n    \"@nao-pon\"\n  ],\n  \"version\":  \"3.9.0\",\n  \"iot_class\": \"local_polling\"\n}\n"
  },
  {
    "path": "custom_components/echonetlite/number.py",
    "content": "import logging\nfrom homeassistant.const import (\n    CONF_ICON,\n    CONF_NAME,\n    CONF_TYPE,\n    CONF_MINIMUM,\n    CONF_MAXIMUM,\n    CONF_UNIT_OF_MEASUREMENT,\n)\nfrom homeassistant.exceptions import InvalidStateError\nfrom homeassistant.components.number import NumberEntity\nfrom pychonet.lib.eojx import EOJX_CLASS\nfrom . import get_name_by_epc_code, get_unit_by_devise_class, get_device_name\nfrom .const import (\n    CONF_DISABLED_DEFAULT,\n    DOMAIN,\n    CONF_FORCE_POLLING,\n    CONF_AS_ZERO,\n    CONF_MAX_OPC,\n    CONF_BYTE_LENGTH,\n    NON_SETUP_SINGLE_ENYITY,\n    TYPE_NUMBER,\n)\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_setup_entry(hass, config, async_add_entities, discovery_info=None):\n    entities = []\n    for entity in hass.data[DOMAIN][config.entry_id]:\n        eojgc = entity[\"instance\"][\"eojgc\"]\n        eojcc = entity[\"instance\"][\"eojcc\"]\n        _enl_op_codes = entity[\"echonetlite\"]._enl_op_codes\n        # configure select entities by looking up full ENL_OP_CODE dict\n        for op_code in list(\n            set(entity[\"instance\"][\"setmap\"])\n            - NON_SETUP_SINGLE_ENYITY.get(eojgc, {}).get(eojcc, set())\n        ):\n            if TYPE_NUMBER in _enl_op_codes.get(op_code, {}).keys():\n                entities.append(\n                    EchonetNumber(\n                        hass,\n                        entity[\"echonetlite\"],\n                        config,\n                        op_code,\n                        _enl_op_codes[op_code],\n                    )\n                )\n\n    async_add_entities(entities, True)\n\n\nclass EchonetNumber(NumberEntity):\n    _attr_translation_key = DOMAIN\n\n    def __init__(self, hass, connector, config, code, options):\n        \"\"\"Initialize the number.\"\"\"\n        self._connector = connector\n        self._config = config\n        self._code = code\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._attr_icon = options.get(CONF_ICON, None)\n        self._attr_name = f\"{config.title} {get_name_by_epc_code(self._connector._eojgc, self._connector._eojcc, self._code, None, self._connector._enl_op_codes.get(self._code, {}).get(CONF_NAME))}\"\n        self._attr_unique_id = (\n            f\"{self._connector._uidi}-{self._code}\"\n            if self._connector._uidi\n            else f\"{self._connector._uid}-{self._code}\"\n        )\n\n        self._options = options[TYPE_NUMBER]\n        self._as_zero = int(options[TYPE_NUMBER].get(CONF_AS_ZERO, 0))\n        self._conf_max = int(options[TYPE_NUMBER][CONF_MAXIMUM])\n        self._byte_length = int(options[TYPE_NUMBER].get(CONF_BYTE_LENGTH, 1))\n\n        self._device_name = get_device_name(connector, config)\n        self._attr_device_class = self._options.get(\n            CONF_TYPE, options.get(CONF_TYPE, None)\n        )\n        self._attr_native_value = self.get_value()\n        self._attr_native_max_value = self.get_max_value()\n        self._attr_native_min_value = self._options.get(CONF_MINIMUM, 0) - self._as_zero\n        self._attr_native_unit_of_measurement = self._options.get(\n            CONF_UNIT_OF_MEASUREMENT, options.get(CONF_UNIT_OF_MEASUREMENT, None)\n        )\n        if not self._attr_native_unit_of_measurement:\n            self._attr_native_unit_of_measurement = get_unit_by_devise_class(\n                self._attr_device_class\n            )\n        self._attr_should_poll = True\n        self._attr_available = True\n\n        self._attr_entity_registry_enabled_default = not bool(\n            options.get(CONF_DISABLED_DEFAULT)\n        )\n\n        self.update_option_listener()\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._instance._eojgc,\n                    self._connector._instance._eojcc,\n                    self._connector._instance._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._connector._instance._eojgc][\n                self._connector._instance._eojcc\n            ],\n            # \"sw_version\": \"\",\n        }\n\n    def get_value(self):\n        value = self._connector._update_data.get(self._code)\n        if value != None:\n            return int(self._connector._update_data.get(self._code)) - self._as_zero\n        else:\n            return None\n\n    def get_max_value(self):\n        max_value = self.get_max_opc_value()\n        if max_value == None:\n            max_value = self._conf_max\n        return max_value - self._as_zero\n\n    def get_max_opc_value(self):\n        max_opc_value = None\n        max_opc = self._options.get(CONF_MAX_OPC)\n        if max_opc:\n            if isinstance(max_opc, list):\n                max_opc_value = self._connector._update_data.get(max_opc[0]).get(\n                    max_opc[1]\n                )\n            else:\n                max_opc_value = self._connector._update_data.get(max_opc)\n            if max_opc_value != None:\n                max_opc_value = int(max_opc_value)\n        return max_opc_value\n\n    async def async_set_native_value(self, value: float) -> None:\n        \"\"\"Update the current value.\"\"\"\n        if await self._connector._instance.setMessage(\n            self._code, int(value + self._as_zero), self._byte_length\n        ):\n            pass\n        else:\n            raise InvalidStateError(\n                \"The state setting is not supported or is an invalid value.\"\n            )\n\n    async def async_update(self):\n        \"\"\"Retrieve latest state.\"\"\"\n        try:\n            await self._connector.async_update()\n        except TimeoutError:\n            pass\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush: bool = False):\n        new_val = self.get_value()\n        changed = (\n            self._attr_native_value != new_val\n            or self._attr_available != self._server_state[\"available\"]\n            or self._attr_native_max_value != self.get_max_value()\n        )\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._attr_native_value = new_val\n            self._attr_native_max_value = self.get_max_value()\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self.async_schedule_update_ha_state(_force)\n\n    def update_option_listener(self):\n        _should_poll = self._code not in self._connector._ntfPropertyMap\n        self._attr_should_poll = (\n            self._connector._user_options.get(CONF_FORCE_POLLING, False) or _should_poll\n        )\n        self._attr_extra_state_attributes = {\"notify\": \"No\" if _should_poll else \"Yes\"}\n        _LOGGER.debug(\n            f\"{self._device_name}({self._code}): _should_poll is {_should_poll}\"\n        )\n"
  },
  {
    "path": "custom_components/echonetlite/quirks/Nichicon/all/02A5.py",
    "content": "from homeassistant.components.sensor.const import (\n    CONF_STATE_CLASS,\n    SensorDeviceClass,\n    SensorStateClass,\n)\nfrom homeassistant.const import (\n    CONF_NAME,\n    CONF_TYPE,\n    CONF_UNIT_OF_MEASUREMENT,\n    UnitOfEnergy,\n)\nfrom pychonet.lib.epc_functions import _int, _signed_int\nfrom ....const import TYPE_DATA_DICT\n\n\ndef _sint_4(edt):\n    res = _signed_int(edt)\n    return res if res >= -2147483647 and res <= 2147483645 else None\n\n\ndef _02A5F5(edt):\n    d1 = d2 = d3 = d4 = None\n    try:\n        d1 = _sint_4(edt[0:4])\n        d2 = _sint_4(edt[4:8])\n        d3 = _sint_4(edt[8:12])\n        d4 = _sint_4(edt[12:16])\n    except:\n        pass\n    finally:\n        return {\n            \"From(-)/To(+) Grid\": d1,\n            \"Household Consumption\": d2,\n            \"Photovoltaic Origin\": d3,\n            \"Other Origin\": d4,\n        }\n\n\ndef _02A5F6(edt):\n    d1 = d2 = None\n    try:\n        d1 = float(_int(edt[0:4])) / 1000\n        d2 = float(_int(edt[4:8])) / 1000\n    except:\n        pass\n    finally:\n        return {\"normal_direction\": d1, \"reverse_direction\": d2}\n\n\nQUIRKS = {\n    0xF5: {\n        \"EPC_FUNCTION\": _02A5F5,\n        \"ENL_OP_CODE\": {\n            CONF_NAME: \"Instantaneous Power\",\n            CONF_TYPE: SensorDeviceClass.POWER,\n            CONF_STATE_CLASS: SensorStateClass.MEASUREMENT,\n            TYPE_DATA_DICT: [\n                \"From(-)/To(+) Grid\",\n                \"Household Consumption\",\n                \"Photovoltaic Origin\",\n                \"Other Origin\",\n            ],\n        },\n    },\n    0xF6: {\n        \"EPC_FUNCTION\": _02A5F6,\n        \"ENL_OP_CODE\": {\n            CONF_NAME: \"Cumulative energy\",\n            CONF_TYPE: SensorDeviceClass.ENERGY,\n            CONF_STATE_CLASS: SensorStateClass.TOTAL_INCREASING,\n            CONF_UNIT_OF_MEASUREMENT: UnitOfEnergy.KILO_WATT_HOUR,\n            TYPE_DATA_DICT: [\"normal_direction\", \"reverse_direction\"],\n        },\n    },\n}\n"
  },
  {
    "path": "custom_components/echonetlite/quirks/Panasonic/all/0135.py",
    "content": "from homeassistant.const import CONF_ICON, CONF_NAME\nfrom pychonet.lib.epc_functions import _int\n\nQUIRKS = {\n    0xF1: {\n        \"EPC_FUNCTION\": [\n            _int,\n            {\n                0x3C: \"High\",\n                0x32: \"Medium\",\n                0x28: \"Low\",\n                0x00: \"Off\",\n            },\n        ],\n        \"ENL_OP_CODE\": {\n            CONF_NAME: \"Humidity setting\",\n            CONF_ICON: \"mdi:air-humidifier\",\n        },\n    },\n}\n"
  },
  {
    "path": "custom_components/echonetlite/select.py",
    "content": "import logging\nfrom homeassistant.const import CONF_ICON, CONF_NAME\nfrom homeassistant.components.select import SelectEntity\nfrom pychonet.HomeAirConditioner import (\n    ENL_AIR_HORZ,\n    ENL_AIR_VERT,\n    ENL_AUTO_DIRECTION,\n    ENL_FANSPEED,\n    ENL_HVAC_MODE,\n    ENL_SWING_MODE,\n)\nfrom . import get_name_by_epc_code, get_device_name\nfrom .const import (\n    CONF_DISABLED_DEFAULT,\n    DOMAIN,\n    CONF_FORCE_POLLING,\n    CONF_ICONS,\n    TYPE_SELECT,\n    NON_SETUP_SINGLE_ENYITY,\n)\nfrom pychonet.lib.eojx import EOJX_CLASS\nfrom pychonet.lib.epc_functions import _swap_dict\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_setup_entry(hass, config, async_add_entities, discovery_info=None):\n    entities = []\n    for entity in hass.data[DOMAIN][config.entry_id]:\n        eojgc = entity[\"instance\"][\"eojgc\"]\n        eojcc = entity[\"instance\"][\"eojcc\"]\n        _enl_op_codes = entity[\"echonetlite\"]._enl_op_codes\n        _non_setup_single_entity = NON_SETUP_SINGLE_ENYITY.get(eojgc, {}).get(\n            eojcc, set()\n        )\n        # configure select entities by looking up full ENL_OP_CODE dict\n        for op_code in list(\n            set(entity[\"instance\"][\"setmap\"])\n            - NON_SETUP_SINGLE_ENYITY.get(eojgc, {}).get(eojcc, set())\n        ):\n            epc_function_data = entity[\"echonetlite\"]._instance.EPC_FUNCTIONS.get(\n                op_code, None\n            )\n            if op_code in _non_setup_single_entity:\n                continue\n            _by_epc_func = (\n                type(epc_function_data) == list\n                and type(epc_function_data[1]) == dict\n                and len(epc_function_data[1]) > 2\n            )\n            _enl_op_code_dict = _enl_op_codes.get(op_code, {})\n            if _by_epc_func or TYPE_SELECT in _enl_op_code_dict.keys():\n                entities.append(\n                    EchonetSelect(\n                        hass,\n                        entity[\"echonetlite\"],\n                        config,\n                        op_code,\n                        _enl_op_code_dict,\n                    )\n                )\n\n    async_add_entities(entities, True)\n\n\nclass EchonetSelect(SelectEntity):\n    _attr_translation_key = DOMAIN\n\n    SELECT_USING_USER_OPTIONS = {\n        \"0x1-0x30\": {\n            ENL_FANSPEED,\n            ENL_SWING_MODE,\n            ENL_AUTO_DIRECTION,\n            ENL_AIR_HORZ,\n            ENL_AIR_VERT,\n            ENL_HVAC_MODE,\n        },\n        \"0x1-0x35\": {\n            ENL_FANSPEED,\n            ENL_SWING_MODE,\n            ENL_AUTO_DIRECTION,\n            ENL_AIR_HORZ,\n            ENL_AIR_VERT,\n        },\n    }\n\n    def __init__(self, hass, connector, config, code, options):\n        \"\"\"Initialize the select.\"\"\"\n        name = get_device_name(connector, config)\n        self._connector = connector\n        self._config = config\n        self._code = code\n        self._optimistic = False\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._sub_state = None\n        if type(options.get(TYPE_SELECT)) == dict:\n            self._options = options[TYPE_SELECT]\n        else:\n            # Read from _instance.EPC FUNCTIONS definition\n            # Swap key, value of _instance.EPC_FUNCTIONS[opc][1]\n            self._options = _swap_dict(connector._instance.EPC_FUNCTIONS[code][1])\n        self._icons = options.get(CONF_ICONS, {})\n        self._attr_icon = options.get(CONF_ICON, None)\n        self._icon_default = self._attr_icon\n        self._attr_options = list(self._options.keys())\n\n        self._user_option_epcs = self.SELECT_USING_USER_OPTIONS.get(\n            hex(self._connector._instance._eojgc)\n            + \"-\"\n            + hex(self._connector._instance._eojcc),\n            set(),\n        ).intersection(set(self._connector._user_options.keys()))\n\n        if self._code in self._user_option_epcs:\n            if self._connector._user_options[code] is not False:\n                self._attr_options = self._connector._user_options[code]\n        self._attr_current_option = self._connector._update_data.get(self._code)\n        self._attr_name = f\"{config.title} {get_name_by_epc_code(self._connector._eojgc, self._connector._eojcc, self._code, None, self._connector._enl_op_codes.get(self._code, {}).get(CONF_NAME))}\"\n        self._attr_unique_id = (\n            f\"{self._connector._uidi}-{self._code}\"\n            if self._connector._uidi\n            else f\"{self._connector._uid}-{self._code}\"\n        )\n        self._device_name = name\n        self._attr_should_poll = True\n        self._attr_available = True\n        self._attr_force_update = False\n\n        self._attr_entity_registry_enabled_default = not bool(\n            options.get(CONF_DISABLED_DEFAULT)\n        )\n\n        self.update_option_listener()\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._instance._eojgc,\n                    self._connector._instance._eojcc,\n                    self._connector._instance._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._connector._instance._eojgc][\n                self._connector._instance._eojcc\n            ],\n            # \"sw_version\": \"\",\n        }\n\n    async def async_select_option(self, option: str):\n        self._attr_current_option = option\n        self.async_schedule_update_ha_state()\n        if not await self._connector._instance.setMessage(\n            self._code, self._options[option]\n        ):\n            # Restore previous state\n            self._attr_current_option = self._connector._update_data.get(self._code)\n            self.async_schedule_update_ha_state()\n\n    async def async_update(self):\n        \"\"\"Retrieve latest state.\"\"\"\n        try:\n            await self._connector.async_update()\n        except TimeoutError:\n            pass\n\n    def update_attr(self):\n        self._attr_options = list(self._options.keys())\n        if self._attr_current_option not in self._attr_options:\n            # maybe data value is raw(int)\n            keys = [\n                k for k, v in self._options.items() if v == self._attr_current_option\n            ]\n            if keys:\n                self._attr_current_option = keys[0]\n        self._attr_icon = self._icons.get(self._attr_current_option, self._icon_default)\n        if self._code in self._user_option_epcs:\n            if self._connector._user_options[self._code] is not False:\n                self._attr_options = self._connector._user_options[self._code]\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush: bool = False):\n        new_val = self._connector._update_data.get(self._code)\n        changed = (\n            new_val is not None and self._attr_current_option != new_val\n        ) or self._attr_available != self._server_state[\"available\"]\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._attr_current_option = new_val\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self.update_attr()\n            self.async_schedule_update_ha_state(_force)\n\n    def update_option_listener(self):\n        _should_poll = self._code not in self._connector._ntfPropertyMap\n        self._attr_should_poll = (\n            self._connector._user_options.get(CONF_FORCE_POLLING, False) or _should_poll\n        )\n        self._attr_extra_state_attributes = {\"notify\": \"No\" if _should_poll else \"Yes\"}\n        _LOGGER.debug(\n            f\"{self._device_name}({self._code}): _should_poll is {_should_poll}\"\n        )\n"
  },
  {
    "path": "custom_components/echonetlite/sensor.py",
    "content": "\"\"\"Support for ECHONETLite sensors.\"\"\"\n\nimport logging\nimport voluptuous as vol\n\nfrom homeassistant.const import (\n    CONF_ICON,\n    CONF_NAME,\n    CONF_SERVICE,\n    CONF_TYPE,\n    CONF_UNIT_OF_MEASUREMENT,\n)\nfrom homeassistant.helpers import config_validation as cv, entity_platform\nfrom homeassistant.components.sensor import SensorEntity\nfrom homeassistant.components.sensor import SensorDeviceClass\nfrom homeassistant.components.binary_sensor import BinarySensorDeviceClass\nfrom homeassistant.exceptions import InvalidStateError, NoEntitySpecifiedError\n\nfrom pychonet.lib.eojx import EOJX_CLASS\nfrom pychonet.lib.epc_functions import EPC_SUPER_FUNCTIONS\n\nfrom . import (\n    get_name_by_epc_code,\n    get_unit_by_devise_class,\n    get_device_name,\n    regist_as_inputs,\n    regist_as_binary_sensor,\n)\nfrom .const import (\n    DOMAIN,\n    ENABLE_SUPER_ENERGY_DEFAULT,\n    ENL_OP_CODES,\n    CONF_STATE_CLASS,\n    ENL_SUPER_CODES,\n    ENL_SUPER_ENERGES,\n    NON_SETUP_SINGLE_ENYITY,\n    TYPE_SWITCH,\n    TYPE_SELECT,\n    TYPE_TIME,\n    TYPE_NUMBER,\n    SERVICE_SET_ON_TIMER_TIME,\n    SERVICE_SET_INT_1B,\n    CONF_FORCE_POLLING,\n    CONF_ENABLE_SUPER_ENERGY,\n    TYPE_DATA_DICT,\n    TYPE_DATA_ARRAY_WITH_SIZE_OPCODE,\n    CONF_DISABLED_DEFAULT,\n    CONF_MULTIPLIER,\n    CONF_MULTIPLIER_OPCODE,\n    CONF_MULTIPLIER_OPTIONAL_OPCODE,\n    CONF_ICON_POSITIVE,\n    CONF_ICON_NEGATIVE,\n    CONF_ICON_ZERO,\n)\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_setup_entry(hass, config, async_add_entities, discovery_info=None):\n    entities = []\n    platform = entity_platform.async_get_current_platform()\n    for entity in hass.data[DOMAIN][config.entry_id]:\n        _LOGGER.debug(f\"Configuring ECHONETLite sensor {entity}\")\n        _LOGGER.debug(\n            f\"Update flags for this sensor are {entity['echonetlite']._update_flags_full_list}\"\n        )\n        eojgc = entity[\"instance\"][\"eojgc\"]\n        eojcc = entity[\"instance\"][\"eojcc\"]\n\n        if entity[\"echonetlite\"]._user_options.get(\n            CONF_ENABLE_SUPER_ENERGY,\n            ENABLE_SUPER_ENERGY_DEFAULT.get(eojgc, {}).get(eojcc, True),\n        ):\n            _enl_super_codes = ENL_SUPER_CODES\n        else:\n            _enl_super_codes = {\n                k: v for k, v in ENL_SUPER_CODES.items() if not k in ENL_SUPER_ENERGES\n            }\n        _enl_op_codes = entity[\"echonetlite\"]._enl_op_codes | _enl_super_codes\n        _epc_functions = (\n            entity[\"echonetlite\"]._instance.EPC_FUNCTIONS | EPC_SUPER_FUNCTIONS\n        )\n        # For all other devices, sensors will be configured but customise if applicable.\n        for op_code in list(\n            set(entity[\"echonetlite\"]._update_flags_full_list)\n            - NON_SETUP_SINGLE_ENYITY.get(eojgc, {}).get(eojcc, set())\n        ):\n            # Check DeviceClass or regist_as_binary_sensor()\n            if isinstance(\n                _enl_op_codes.get(op_code, {}).get(CONF_TYPE), BinarySensorDeviceClass\n            ) or regist_as_binary_sensor(_epc_functions.get(op_code, None)):\n                continue\n            # Is settable\n            _is_settable = op_code in entity[\"instance\"][\"setmap\"]\n            # Conf Keys list\n            _keys = _enl_op_codes.get(op_code, {}).keys()\n            # For backward compatibility (Deprecated)\n            _has_conf_service = CONF_SERVICE in _keys\n            # Check this op_code will be configured as input(switch, select ot time) entity\n            if (\n                _is_settable\n                and not _has_conf_service\n                and regist_as_inputs(_epc_functions.get(op_code, None))\n            ):\n                continue\n            # Configuration check with ENL_OP_CODE definition\n            if len(_keys):\n                if (\n                    _is_settable\n                    and not _has_conf_service\n                    and (\n                        TYPE_SWITCH in _keys\n                        or TYPE_SELECT in _keys\n                        or TYPE_TIME in _keys\n                        or TYPE_NUMBER in _keys\n                    )\n                ):\n                    continue  # dont configure as sensor, it will be configured as switch, select or time instead.\n\n                # For backward compatibility (Deprecated)\n                if (\n                    _is_settable and _has_conf_service\n                ):  # Some devices support advanced service calls.\n                    _enl_op_codes[op_code][CONF_DISABLED_DEFAULT] = True\n                    for service_name in _enl_op_codes.get(op_code, {}).get(\n                        CONF_SERVICE\n                    ):\n                        if service_name == SERVICE_SET_ON_TIMER_TIME:\n                            platform.async_register_entity_service(\n                                service_name,\n                                {vol.Required(\"timer_time\"): cv.time_period},\n                                \"async_\" + service_name,\n                            )\n                        elif service_name == SERVICE_SET_INT_1B:\n                            platform.async_register_entity_service(\n                                service_name,\n                                {\n                                    vol.Required(\"value\"): cv.positive_int,\n                                    vol.Optional(\n                                        \"epc\", default=op_code\n                                    ): cv.positive_int,\n                                },\n                                \"async_\" + service_name,\n                            )\n\n                if TYPE_DATA_DICT in _keys:\n                    type_data = _enl_op_codes.get(op_code, {}).get(TYPE_DATA_DICT)\n                    if isinstance(type_data, list):\n                        for attr_key in type_data:\n                            entities.append(\n                                EchonetSensor(\n                                    entity[\"echonetlite\"],\n                                    config,\n                                    op_code,\n                                    _enl_op_codes.get(op_code) | {\"dict_key\": attr_key},\n                                    hass,\n                                )\n                            )\n                        continue\n                    else:\n                        continue\n                if TYPE_DATA_ARRAY_WITH_SIZE_OPCODE in _keys:\n                    array_size_op_code = _enl_op_codes[op_code][\n                        TYPE_DATA_ARRAY_WITH_SIZE_OPCODE\n                    ]\n                    array_max_size = await entity[\"echonetlite\"]._instance.update(\n                        array_size_op_code\n                    )\n                    for x in range(0, array_max_size):\n                        attr = _enl_op_codes[op_code].copy()\n                        attr[\"accessor_index\"] = x\n                        attr[\"accessor_lambda\"] = lambda value, index: (\n                            value[\"values\"][index] if index < value[\"range\"] else None\n                        )\n                        entities.append(\n                            EchonetSensor(\n                                entity[\"echonetlite\"],\n                                config,\n                                op_code,\n                                attr,\n                            )\n                        )\n                    continue\n                else:\n                    entities.append(\n                        EchonetSensor(\n                            entity[\"echonetlite\"],\n                            config,\n                            op_code,\n                            _enl_op_codes.get(\n                                op_code,\n                                ENL_OP_CODES[\"default\"] | {CONF_DISABLED_DEFAULT: True},\n                            ),\n                            hass,\n                        )\n                    )\n                continue\n            entities.append(\n                EchonetSensor(\n                    entity[\"echonetlite\"],\n                    config,\n                    op_code,\n                    ENL_OP_CODES[\"default\"],\n                    hass,\n                )\n            )\n    async_add_entities(entities, True)\n\n\nclass EchonetSensor(SensorEntity):\n    \"\"\"Representation of an ECHONETLite Temperature Sensor.\"\"\"\n\n    _attr_translation_key = DOMAIN\n\n    def __init__(self, connector, config, op_code, attributes, hass=None) -> None:\n        \"\"\"Initialize the sensor.\"\"\"\n        name = get_device_name(connector, config)\n        self._connector = connector\n        self._op_code = op_code\n        self._sensor_attributes = attributes\n        self._eojgc = self._connector._eojgc\n        self._eojcc = self._connector._eojcc\n        self._eojci = self._connector._eojci\n        self._attr_unique_id = (\n            f\"{self._connector._uidi}-{self._op_code}\"\n            if self._connector._uidi\n            else f\"{self._connector._uid}-{self._eojgc}-{self._eojcc}-{self._eojci}-{self._op_code}\"\n        )\n        self._device_name = name\n        self._state_value = None\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._hass = hass\n\n        _attr_keys = self._sensor_attributes.keys()\n\n        self._attr_icon = self._sensor_attributes.get(CONF_ICON)\n        self._attr_device_class = self._sensor_attributes.get(CONF_TYPE)\n        self._attr_state_class = self._sensor_attributes.get(CONF_STATE_CLASS)\n\n        # Create name based on sensor description from EPC codes, super class codes or fallback to using the sensor type\n        self._attr_name = f\"{name} {get_name_by_epc_code(self._eojgc, self._eojcc, self._op_code, self._attr_device_class, self._connector._enl_op_codes.get(self._op_code, {}).get(CONF_NAME))}\"\n\n        if \"dict_key\" in _attr_keys:\n            self._attr_unique_id += f'-{self._sensor_attributes[\"dict_key\"]}'\n            if type(self._sensor_attributes[TYPE_DATA_DICT]) == int:\n                # As of Version 3.8.0, no configuration is defined that uses this definition.\n                self._attr_name += f' {str(self._sensor_attributes[\"accessor_index\"] + 1).zfill(len(str(self._sensor_attributes[TYPE_DATA_DICT])))}'\n            else:\n                self._attr_name += f' {self._sensor_attributes[\"dict_key\"]}'\n\n        if \"accessor_index\" in _attr_keys:\n            self._attr_unique_id += f'-{self._sensor_attributes[\"accessor_index\"]}'\n            self._attr_name += f' {str(self._sensor_attributes[\"accessor_index\"] + 1).zfill(len(str(self._sensor_attributes[TYPE_DATA_ARRAY_WITH_SIZE_OPCODE])))}'\n\n        self._attr_native_unit_of_measurement = self._sensor_attributes.get(\n            CONF_UNIT_OF_MEASUREMENT\n        )\n        if not self._attr_native_unit_of_measurement:\n            self._attr_native_unit_of_measurement = get_unit_by_devise_class(\n                self._attr_device_class\n            )\n        self._attr_entity_registry_enabled_default = not bool(\n            self._sensor_attributes.get(CONF_DISABLED_DEFAULT)\n        )\n\n        self._attr_should_poll = True\n        self._attr_available = True\n\n        self.update_option_listener()\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._eojgc,\n                    self._connector._eojcc,\n                    self._connector._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._eojgc][self._eojcc],\n            # \"sw_version\": \"\",\n        }\n\n    def get_attr_native_value(self):\n        \"\"\"Return the state of the sensor.\"\"\"\n        if self._op_code in self._connector._update_data:\n            new_val = self._connector._update_data[self._op_code]\n            if \"dict_key\" in self._sensor_attributes:\n                if hasattr(new_val, \"get\"):\n                    self._state_value = new_val.get(self._sensor_attributes[\"dict_key\"])\n                else:\n                    self._state_value = None\n            elif \"accessor_lambda\" in self._sensor_attributes:\n                self._state_value = self._sensor_attributes[\"accessor_lambda\"](\n                    new_val, self._sensor_attributes[\"accessor_index\"]\n                )\n            else:\n                self._state_value = new_val\n\n            if self._state_value is None:\n                return None\n\n            # interactive icon\n            if CONF_ICON_POSITIVE in self._sensor_attributes:\n                if self._state_value is None and self._state_value > 0:\n                    self._sensor_attributes[CONF_ICON] = self._sensor_attributes[\n                        CONF_ICON_POSITIVE\n                    ]\n                elif self._state_value is None and self._state_value < 0:\n                    self._sensor_attributes[CONF_ICON] = self._sensor_attributes[\n                        CONF_ICON_NEGATIVE\n                    ]\n                else:\n                    self._sensor_attributes[CONF_ICON] = self._sensor_attributes[\n                        CONF_ICON_ZERO\n                    ]\n\n            # apply coefficients\n            if (\n                CONF_MULTIPLIER in self._sensor_attributes\n                or CONF_MULTIPLIER_OPCODE in self._sensor_attributes\n                or CONF_MULTIPLIER_OPTIONAL_OPCODE in self._sensor_attributes\n            ):\n                new_val = self._state_value\n                if CONF_MULTIPLIER in self._sensor_attributes:\n                    new_val = new_val * self._sensor_attributes[CONF_MULTIPLIER]\n                if CONF_MULTIPLIER_OPCODE in self._sensor_attributes:\n                    multiplier_opcode = self._sensor_attributes[CONF_MULTIPLIER_OPCODE]\n                    if (\n                        multiplier_opcode in self._connector._update_data\n                        and self._connector._update_data[multiplier_opcode] is not None\n                    ):\n                        new_val = (\n                            new_val * self._connector._update_data[multiplier_opcode]\n                        )\n                    else:\n                        return None\n                if CONF_MULTIPLIER_OPTIONAL_OPCODE in self._sensor_attributes:\n                    multiplier_opcode = self._sensor_attributes[\n                        CONF_MULTIPLIER_OPTIONAL_OPCODE\n                    ]\n                    if (\n                        multiplier_opcode in self._connector._update_data\n                        and self._connector._update_data[multiplier_opcode] is not None\n                    ):\n                        new_val = (\n                            new_val * self._connector._update_data[multiplier_opcode]\n                        )\n                return new_val\n            elif self._attr_device_class in [\n                SensorDeviceClass.TEMPERATURE,\n                SensorDeviceClass.HUMIDITY,\n            ]:\n                if self._state_value in [126, 253]:\n                    return None\n                else:\n                    return self._state_value\n            elif self._attr_device_class == SensorDeviceClass.POWER:\n                # Underflow (less than 1 W)\n                if self._state_value == 65534:\n                    return 1\n                else:\n                    return self._state_value\n            elif self._op_code in self._connector._update_data:\n                if isinstance(self._state_value, (int, float)):\n                    return self._state_value\n                if len(self._state_value) < 255:\n                    return self._state_value\n                else:\n                    return None\n        return None\n\n    async def async_update(self):\n        \"\"\"Retrieve latest state.\"\"\"\n        try:\n            await self._connector.async_update()\n            self._attr_native_value = self.get_attr_native_value()\n        except TimeoutError:\n            pass\n\n    async def async_set_on_timer_time(self, timer_time):\n        val = str(timer_time).split(\":\")\n        mes = {\"EPC\": 0x91, \"PDC\": 0x02, \"EDT\": int(val[0]) * 256 + int(val[1])}\n        if await self._connector._instance.setMessages([mes]):\n            pass\n        else:\n            raise InvalidStateError(\n                \"The state setting is not supported or is an invalid value.\"\n            )\n\n    async def async_set_value_int_1b(self, value, epc=None):\n        if epc:\n            value = int(value)\n            if await self._connector._instance.setMessage(epc, value):\n                pass\n            else:\n                raise InvalidStateError(\n                    \"The state setting is not supported or is an invalid value.\"\n                )\n        else:\n            raise NoEntitySpecifiedError(\n                \"The required parameter EPC has not been specified.\"\n            )\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush: bool = False):\n        new_val = self._connector._update_data.get(self._op_code)\n        if \"dict_key\" in self._sensor_attributes:\n            if hasattr(new_val, \"get\"):\n                new_val = new_val.get(self._sensor_attributes[\"dict_key\"])\n            else:\n                new_val = None\n        if \"accessor_lambda\" in self._sensor_attributes:\n            new_val = self._sensor_attributes[\"accessor_lambda\"](\n                new_val, self._sensor_attributes[\"accessor_index\"]\n            )\n        changed = (\n            new_val is not None and self._state_value != new_val\n        ) or self._attr_available != self._server_state[\"available\"]\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._state_value = new_val\n            self._attr_native_value = self.get_attr_native_value()\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self.async_schedule_update_ha_state(_force)\n\n    def update_option_listener(self):\n        _should_poll = self._op_code not in self._connector._ntfPropertyMap\n        self._attr_should_poll = (\n            self._connector._user_options.get(CONF_FORCE_POLLING, False) or _should_poll\n        )\n        self._attr_extra_state_attributes = {\"notify\": \"No\" if _should_poll else \"Yes\"}\n        _LOGGER.debug(\n            f\"{self._attr_name}({self._op_code}): _should_poll is {_should_poll}\"\n        )\n"
  },
  {
    "path": "custom_components/echonetlite/services.yaml",
    "content": "set_on_timer_time:\n  description: 'Set the time of the on-timer of the device.'\n  target:\n    entity:\n      domain: sensor\n      integration: echonetlite\n    device:\n      integration: echonetlite\n  fields:\n    timer_time:\n      description: 'Time notation in \"hour:minute\" format.'\n      example: '\"21:00\"'\n      required: true\n    entity_id:\n      description: 'On-timer time sensor entity ID.'\n      example: '\"sensor.echonetlite_set_value_of_on_timer_time\"'\nset_value_int_1b:\n  description: 'Sets the specified sensor entity to an integer value.'\n  target:\n    entity:\n      domain: sensor\n      integration: echonetlite\n  fields:\n    value:\n      description: 'Valid integer value for the specified entity.'\n      example: '42'\n      required: true\n    entity_id:\n      description: 'Integer sensor entity ID.'\n      example: '\"sensor.echonetlite_set_value_of_hot_water_temperature\"'"
  },
  {
    "path": "custom_components/echonetlite/strings.json",
    "content": "{\n    \"config\": {\n        \"step\": {\n            \"user\": {\n                \"title\": \"Device IP address and name setting (Auto Discovery)\",\n                \"description\": \"For automatic detection, after pressing the Submit button, please perform operations such as turning the device on or off within 30 seconds. If you know the IP address, enter the IP address and name.\",\n                \"data\": {\n                    \"host\": \"[%key:common::config_flow::data::host%]\",\n                    \"title\": \"[%key:common::config_flow::data::title%]\"\n                }\n            },\n            \"user_man\": {\n                \"title\": \"Device IP address and name setting\",\n                \"description\": \"\",\n                \"data\": {\n                    \"host\": \"[%key:common::config_flow::data::host%]\",\n                    \"title\": \"[%key:common::config_flow::data::title%]\"\n                }\n            }\n        },\n        \"error\": {\n            \"cannot_connect\": \"[%key:common::config_flow::error::cannot_connect%]\",\n            \"invalid_auth\": \"[%key:common::config_flow::error::invalid_auth%]\",\n            \"unknown\": \"[%key:common::config_flow::error::unknown%]\",\n            \"not_found\": \"Device could not be detected.\",\n            \"already_configured\": \"[%key:common::config_flow::abort::already_configured_device%]\"\n        },\n        \"abort\": {\n            \"already_configured\": \"[%key:common::config_flow::abort::already_configured_device%]\"\n        },\n        \"progress\": {\n            \"discover\": \"Discovering ECHONETLite devices..\"\n        }\n    },\n    \"options\": {\n        \"error\": {\n            \"invalid_path\": \"Invalid Setting\"\n        },\n        \"step\": {\n            \"init\": {\n                \"title\": \"ECHONET Lite HVAC Options\",\n                \"data\": {\n                    \"fan_settings\": \"Configure fan settings\",\n                    \"swing_horiz\": \"Configure Horizontal Swing Settings\",\n                    \"swing_vert\": \"Configure Vertical Swing Settings\",\n                    \"auto_direction\": \"Configure Auto Direction\",\n                    \"swing_mode\": \"Configure Swing Mode\",\n                    \"min_temp_heat\": \"Configure Minimum Temperature for Heating Operation\",\n                    \"max_temp_heat\": \"Configure Maximum Temperature for Heating Operation\",\n                    \"min_temp_cool\": \"Configure Minimum Temperature for Cooling Operation\",\n                    \"max_temp_cool\": \"Configure Maximum Temperature for Cooling Operation\",\n                    \"min_temp_auto\": \"Configure Minimum Temperature for Automatic Operation\",\n                    \"max_temp_auto\": \"Configure Maximum Temperature for Automatic Operation\"\n                },\n                \"description\": \"Configure Fan Settings\"\n            }\n        }\n    },\n    \"entity\": {\n        \"select\": {\n            \"echonetlite\": {\n                \"state\": {\n                    \"auto\": \"Auto\",\n                    \"minimum\": \"Minimum\",\n                    \"low\": \"Low\",\n                    \"medium-low\": \"Medium-Low\",\n                    \"medium\": \"Medium\",\n                    \"medium-high\": \"Medium-High\",\n                    \"high\": \"High\",\n                    \"very-high\": \"Very-High\",\n                    \"max\": \"Max\",\n                    \"rc-right\": \"Right Center + Right\",\n                    \"left-lc\": \"Left + Left Center\",\n                    \"lc-center-rc\": \"Left + Center + Right Center\",\n                    \"left-lc-rc-right\": \"Left + Left Center + Right Center + Right\",\n                    \"right\": \"Right\",\n                    \"rc\": \"Right Center\",\n                    \"center\": \"Center\",\n                    \"center-right\": \"Center + Right\",\n                    \"center-rc\": \"Center + Right Center\",\n                    \"center-rc-right\": \"Center + Right Center + Right\",\n                    \"lc\": \"Left Center\",\n                    \"lc-right\": \"Left Center + Right\",\n                    \"lc-rc\": \"Left Center + Right Center\",\n                    \"lc-rc-right\": \"Left Center + Right Center + Right\",\n                    \"lc-center\": \"Left Center + Center\",\n                    \"lc-center-right\": \"Left Center + Center + Right\",\n                    \"lc-center-rc-right\": \"Left Center + Center + Right Center + Right\",\n                    \"left\": \"Left\",\n                    \"left-right\": \"Left + Right\",\n                    \"left-rc\": \"Left + Right Center\",\n                    \"left-rc-right\": \"Left + Right Center + Right\",\n                    \"left-center\": \"Left + Center\",\n                    \"left-center-right\": \"Left + Center + Right\",\n                    \"left-center-rc\": \"Left + Center + Right Center\",\n                    \"left-center-rc-right\": \"Left + Center + Right Center + Right\",\n                    \"left-lc-right\": \"Left + Left Center + Right\",\n                    \"left-lc-rc\": \"Left + Left Center + Right Center\",\n                    \"left-lc-center\": \"Left + Left Center + Center\",\n                    \"left-lc-center-right\": \"Left + Left Center + Center + Right\",\n                    \"left-lc-center-rc\": \"Left + Left Center + Center + Right Center\",\n                    \"left-lc-center-rc-right\": \"Left + Left Center + Center + Right Center + Right\",\n                    \"upper\": \"Upper\",\n                    \"upper-central\": \"Upper Central\",\n                    \"central\": \"Central\",\n                    \"lower-central\": \"Lower Central\",\n                    \"lower\": \"Lower\",\n                    \"not-used\": \"Not Used (Off)\",\n                    \"vert\": \"Vertical\",\n                    \"horiz\": \"Horizontal\",\n                    \"vert-horiz\": \"Vertical-Horizontal\",\n                    \"non-auto\": \"Non-Auto\",\n                    \"auto-vert\": \"Auto Vertical\",\n                    \"auto-horiz\": \"Auto Horizontal\"\n                }\n            }\n        },\n        \"climate\": {\n            \"echonetlite\": {\n                \"state_attributes\": {\n                    \"fan_mode\": {\n                        \"state\": {\n                            \"auto\": \"Auto\",\n                            \"minimum\": \"Minimum\",\n                            \"low\": \"Low\",\n                            \"medium-low\": \"Medium-Low\",\n                            \"medium\": \"Medium\",\n                            \"medium-high\": \"Medium-High\",\n                            \"high\": \"High\",\n                            \"very-high\": \"Very-High\",\n                            \"max\": \"Max\"\n                        }\n                    },\n                    \"swing_mode\": {\n                        \"name\": \"Air direction\",\n                        \"state\": {\n                            \"rc-right\": \"Right Center + Right\",\n                            \"left-lc\": \"Left + Left Center\",\n                            \"lc-center-rc\": \"Left + Center + Right Center\",\n                            \"left-lc-rc-right\": \"Left + Left Center + Right Center + Right\",\n                            \"right\": \"Right\",\n                            \"rc\": \"Right Center\",\n                            \"center\": \"Center\",\n                            \"center-right\": \"Center + Right\",\n                            \"center-rc\": \"Center + Right Center\",\n                            \"center-rc-right\": \"Center + Right Center + Right\",\n                            \"lc\": \"Left Center\",\n                            \"lc-right\": \"Left Center + Right\",\n                            \"lc-rc\": \"Left Center + Right Center\",\n                            \"lc-rc-right\": \"Left Center + Right Center + Right\",\n                            \"lc-center\": \"Left Center + Center\",\n                            \"lc-center-right\": \"Left Center + Center + Right\",\n                            \"lc-center-rc-right\": \"Left Center + Center + Right Center + Right\",\n                            \"left\": \"Left\",\n                            \"left-right\": \"Left + Right\",\n                            \"left-rc\": \"Left + Right Center\",\n                            \"left-rc-right\": \"Left + Right Center + Right\",\n                            \"left-center\": \"Left + Center\",\n                            \"left-center-right\": \"Left + Center + Right\",\n                            \"left-center-rc\": \"Left + Center + Right Center\",\n                            \"left-center-rc-right\": \"Left + Center + Right Center + Right\",\n                            \"left-lc-right\": \"Left + Left Center + Right\",\n                            \"left-lc-rc\": \"Left + Left Center + Right Center\",\n                            \"left-lc-center\": \"Left + Left Center + Center\",\n                            \"left-lc-center-right\": \"Left + Left Center + Center + Right\",\n                            \"left-lc-center-rc\": \"Left + Left Center + Center + Right Center\",\n                            \"left-lc-center-rc-right\": \"Left + Left Center + Center + Right Center + Right\",\n                            \"upper\": \"Upper\",\n                            \"upper-central\": \"Upper Central\",\n                            \"central\": \"Central\",\n                            \"lower-central\": \"Lower Central\",\n                            \"lower\": \"Lower\",\n                            \"not-used\": \"Not Used (Off)\",\n                            \"vert\": \"Vertical\",\n                            \"horiz\": \"Horizontal\",\n                            \"vert-horiz\": \"Vertical-Horizontal\",\n                            \"auto\": \"Auto\",\n                            \"non-auto\": \"Non-Auto\",\n                            \"auto-vert\": \"Auto Vertical\",\n                            \"auto-horiz\": \"Auto Horizontal\"\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "custom_components/echonetlite/switch.py",
    "content": "import asyncio\nimport logging\nfrom homeassistant.const import CONF_ICON, CONF_SERVICE_DATA, CONF_NAME\nfrom homeassistant.components.switch import SwitchEntity\nfrom . import get_name_by_epc_code, get_device_name\nfrom .const import (\n    CONF_DISABLED_DEFAULT,\n    DOMAIN,\n    CONF_ON_VALUE,\n    CONF_OFF_VALUE,\n    DATA_STATE_ON,\n    DATA_STATE_OFF,\n    NON_SETUP_SINGLE_ENYITY,\n    SWITCH_POWER,\n    CONF_ENSURE_ON,\n    TYPE_SWITCH,\n    TYPE_NUMBER,\n    ENL_STATUS,\n    CONF_FORCE_POLLING,\n)\nfrom pychonet.lib.eojx import EOJX_CLASS\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_setup_entry(hass, config, async_add_entities, discovery_info=None):\n    entities = []\n    for entity in hass.data[DOMAIN][config.entry_id]:\n        eojgc = entity[\"instance\"][\"eojgc\"]\n        eojcc = entity[\"instance\"][\"eojcc\"]\n        set_enl_status = False\n        _enl_op_codes = entity[\"echonetlite\"]._enl_op_codes\n        # configure switch entities by looking up full ENL_OP_CODE dict\n        for op_code in list(\n            set(entity[\"instance\"][\"setmap\"])\n            - NON_SETUP_SINGLE_ENYITY.get(eojgc, {}).get(eojcc, set())\n        ):\n            epc_function_data = entity[\"echonetlite\"]._instance.EPC_FUNCTIONS.get(\n                op_code, None\n            )\n            _by_epc_func = (\n                type(epc_function_data) == list\n                and type(epc_function_data[1]) == dict\n                and len(epc_function_data[1]) == 2\n            )\n            _enl_op_code_dict = _enl_op_codes.get(op_code, {})\n            if _by_epc_func or TYPE_SWITCH in _enl_op_code_dict.keys():\n                entities.append(\n                    EchonetSwitch(\n                        hass,\n                        entity[\"echonetlite\"],\n                        config,\n                        op_code,\n                        _enl_op_code_dict,\n                    )\n                )\n                if op_code == ENL_STATUS:\n                    set_enl_status = True\n            if (\n                switch_conf := _enl_op_codes.get(op_code, {})\n                .get(TYPE_NUMBER, {})\n                .get(TYPE_SWITCH)\n            ):\n                switch_conf.update(_enl_op_codes[op_code].copy())\n                entities.append(\n                    EchonetSwitch(\n                        hass,\n                        entity[\"echonetlite\"],\n                        config,\n                        op_code,\n                        switch_conf,\n                    )\n                )\n        # Auto configure of the power switch\n        if (eojgc == 0x01 and eojcc in (0x30, 0x35)) or (\n            eojgc == 0x02 and eojcc in (0x90, 0x91)\n        ):\n            # Home air conditioner, Air cleaner, General Lighting, Single Function Lighting\n            continue\n        if not set_enl_status and ENL_STATUS in entity[\"instance\"][\"setmap\"]:\n            entities.append(\n                EchonetSwitch(\n                    hass,\n                    entity[\"echonetlite\"],\n                    config,\n                    ENL_STATUS,\n                    {\n                        CONF_ICON: \"mdi:power-settings\",\n                        CONF_SERVICE_DATA: SWITCH_POWER,\n                    },\n                )\n            )\n    async_add_entities(entities, True)\n\n\nclass EchonetSwitch(SwitchEntity):\n    def __init__(self, hass, connector, config, code, options):\n        \"\"\"Initialize the switch.\"\"\"\n        name = get_device_name(connector, config)\n        self._connector = connector\n        self._config = config\n        self._code = code\n        self._options = options\n        epc_function_data = connector._instance.EPC_FUNCTIONS.get(code, None)\n        if type(epc_function_data) == list:\n            data_keys = list(epc_function_data[1].keys())\n            data_items = list(epc_function_data[1].values())\n            self._options.update(\n                {\n                    CONF_SERVICE_DATA: {\n                        DATA_STATE_ON: data_keys[0],\n                        DATA_STATE_OFF: data_keys[1],\n                    },\n                    CONF_ON_VALUE: data_items[0],\n                    CONF_OFF_VALUE: data_items[1],\n                }\n            )\n        self._on_value = self._options.get(CONF_ON_VALUE, DATA_STATE_ON)\n        self._on_vals = [\n            self._on_value,\n            self._options[CONF_SERVICE_DATA][DATA_STATE_ON],\n            hex(self._options[CONF_SERVICE_DATA][DATA_STATE_ON])[2:],\n        ]\n        self._from_number = True if options.get(TYPE_NUMBER) else False\n        self._attr_name = f\"{config.title} {get_name_by_epc_code(self._connector._eojgc, self._connector._eojcc, self._code, None, self._connector._enl_op_codes.get(self._code, {}).get(CONF_NAME))}\"\n        self._attr_icon = options.get(CONF_ICON)\n        self._attr_unique_id = (\n            f\"{self._connector._uidi}-{self._code}\"\n            if self._connector._uidi\n            else f\"{self._connector._uid}-{self._connector._eojgc}-{self._connector._eojcc}-{self._connector._eojci}-{self._code}\"\n        )\n        if self._from_number:\n            self._attr_unique_id += \"-switch\"\n            self._attr_name += \" \" + options.get(CONF_NAME, \"Switch\")\n        self._device_name = name\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._attr_is_on = self._connector._update_data[self._code] in self._on_vals\n        self._attr_should_poll = True\n        self._attr_available = True\n\n        self._attr_entity_registry_enabled_default = not bool(\n            options.get(CONF_DISABLED_DEFAULT)\n        )\n\n        self.update_option_listener()\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._instance._eojgc,\n                    self._connector._instance._eojcc,\n                    self._connector._instance._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._connector._instance._eojgc][\n                self._connector._instance._eojcc\n            ],\n        }\n\n    async def async_turn_on(self, **kwargs) -> None:\n        \"\"\"Turn switch on.\"\"\"\n        main_sw_code = None\n        # Check ensure turn on switch.\n        # For some devices this ensures the main switch is switched on firs\n        if CONF_ENSURE_ON in self._options:\n            main_sw_code = self._options[CONF_ENSURE_ON]\n        # Turn on the specified switch\n        if (\n            main_sw_code is not None\n            and self._connector._update_data[main_sw_code] != DATA_STATE_ON\n        ):\n            if not await self._connector._instance.setMessage(\n                main_sw_code, SWITCH_POWER[DATA_STATE_ON]\n            ):\n                # Can't turn on main switch\n                return\n            # Wait about 2 seconds until the On state is stabilized on the device side\n            await asyncio.sleep(2)\n\n        if (\n            main_sw_code is None\n            or self._connector._update_data[main_sw_code] == DATA_STATE_ON\n        ):\n            await self._connector._instance.setMessage(\n                self._code, self._options[CONF_SERVICE_DATA][DATA_STATE_ON]\n            )\n\n    async def async_turn_off(self, **kwargs) -> None:\n        \"\"\"Turn switch off.\"\"\"\n        await self._connector._instance.setMessage(\n            self._code, self._options[CONF_SERVICE_DATA][DATA_STATE_OFF]\n        )\n\n    async def async_update(self):\n        \"\"\"Retrieve latest state.\"\"\"\n        try:\n            await self._connector.async_update()\n        except TimeoutError:\n            pass\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush: bool = False):\n        new_val = self._connector._update_data[self._code] in self._on_vals\n        changed = (\n            self._attr_is_on != new_val\n            or self._attr_available != self._server_state[\"available\"]\n        )\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._attr_is_on = new_val\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self.async_schedule_update_ha_state(_force)\n\n    def update_option_listener(self):\n        _should_poll = self._code not in self._connector._ntfPropertyMap\n        self._attr_should_poll = (\n            self._connector._user_options.get(CONF_FORCE_POLLING, False) or _should_poll\n        )\n        self._attr_extra_state_attributes = {\"notify\": \"No\" if _should_poll else \"Yes\"}\n        _LOGGER.debug(\n            f\"{self._device_name}({self._code}): _should_poll is {_should_poll}\"\n        )\n"
  },
  {
    "path": "custom_components/echonetlite/time.py",
    "content": "import logging\nimport datetime\nfrom datetime import time\nfrom homeassistant.const import CONF_ICON, CONF_NAME\nfrom homeassistant.components.time import TimeEntity\nfrom homeassistant.exceptions import InvalidStateError\nfrom . import get_name_by_epc_code, get_device_name\nfrom .const import (\n    CONF_DISABLED_DEFAULT,\n    DOMAIN,\n    CONF_FORCE_POLLING,\n    ENL_SUPER_CODES,\n    NON_SETUP_SINGLE_ENYITY,\n    TYPE_TIME,\n)\nfrom pychonet.lib.eojx import EOJX_CLASS\nfrom pychonet.lib.epc_functions import _hh_mm\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def async_setup_entry(hass, config, async_add_entities, discovery_info=None):\n    entities = []\n    for entity in hass.data[DOMAIN][config.entry_id]:\n        eojgc = entity[\"instance\"][\"eojgc\"]\n        eojcc = entity[\"instance\"][\"eojcc\"]\n        _enl_op_codes = entity[\"echonetlite\"]._enl_op_codes | ENL_SUPER_CODES\n        # configure select entities by looking up full ENL_OP_CODE dict\n        for op_code in list(\n            set(entity[\"instance\"][\"setmap\"])\n            - NON_SETUP_SINGLE_ENYITY.get(eojgc, {}).get(eojcc, set())\n        ):\n            epc_function_data = entity[\"echonetlite\"]._instance.EPC_FUNCTIONS.get(\n                op_code, None\n            )\n            _by_epc_func = (\n                type(epc_function_data) == list and epc_function_data[0] == _hh_mm\n            ) or (callable(epc_function_data) and epc_function_data == _hh_mm)\n            if _by_epc_func or TYPE_TIME in _enl_op_codes.get(op_code, {}).keys():\n                entities.append(\n                    EchonetTime(\n                        hass,\n                        entity[\"echonetlite\"],\n                        config,\n                        op_code,\n                        _enl_op_codes.get(op_code, {}),\n                    )\n                )\n\n    async_add_entities(entities, True)\n\n\nclass EchonetTime(TimeEntity):\n    _attr_translation_key = DOMAIN\n\n    def __init__(self, hass, connector, config, code, options, device_name=None):\n        \"\"\"Initialize the time.\"\"\"\n        self._connector = connector\n        self._config = config\n        self._code = code\n        self._server_state = self._connector._api._state[\n            self._connector._instance._host\n        ]\n        self._attr_icon = options.get(CONF_ICON, None)\n        self._attr_name = f\"{config.title} {get_name_by_epc_code(self._connector._eojgc, self._connector._eojcc, self._code, None, self._connector._enl_op_codes.get(self._code, {}).get(CONF_NAME))}\"\n        self._attr_unique_id = (\n            f\"{self._connector._uidi}-{self._code}\"\n            if self._connector._uidi\n            else f\"{self._connector._uid}-{self._code}\"\n        )\n\n        self._device_name = get_device_name(connector, config)\n        self._attr_native_value = self.get_time()\n        self._attr_should_poll = True\n        self._attr_available = True\n\n        self._attr_entity_registry_enabled_default = not bool(\n            options.get(CONF_DISABLED_DEFAULT)\n        )\n\n        self.update_option_listener()\n\n    @property\n    def device_info(self):\n        return {\n            \"identifiers\": {\n                (\n                    DOMAIN,\n                    self._connector._uid,\n                    self._connector._instance._eojgc,\n                    self._connector._instance._eojcc,\n                    self._connector._instance._eojci,\n                )\n            },\n            \"name\": self._device_name,\n            \"manufacturer\": self._connector._manufacturer\n            + (\n                \" \" + self._connector._host_product_code\n                if self._connector._host_product_code\n                else \"\"\n            ),\n            \"model\": EOJX_CLASS[self._connector._instance._eojgc][\n                self._connector._instance._eojcc\n            ],\n            # \"sw_version\": \"\",\n        }\n\n    def get_time(self):\n        hh_mm = self._connector._update_data.get(self._code)\n        if hh_mm != None:\n            val = hh_mm.split(\":\")\n            time_obj = datetime.time(int(val[0]), int(val[1]))\n        else:\n            time_obj = None\n        return time_obj\n\n    async def async_set_value(self, value: time) -> None:\n        \"\"\"Update the current value.\"\"\"\n        h = int(value.hour)\n        m = int(value.minute)\n        mes = {\"EPC\": self._code, \"PDC\": 0x02, \"EDT\": h * 256 + m}\n        if await self._connector._instance.setMessages([mes]):\n            pass\n        else:\n            raise InvalidStateError(\n                \"The state setting is not supported or is an invalid value.\"\n            )\n\n    async def async_update(self):\n        \"\"\"Retrieve latest state.\"\"\"\n        try:\n            await self._connector.async_update()\n        except TimeoutError:\n            pass\n\n    async def async_added_to_hass(self):\n        \"\"\"Register callbacks.\"\"\"\n        self._connector.add_update_option_listener(self.update_option_listener)\n        self._connector.register_async_update_callbacks(self.async_update_callback)\n\n    async def async_update_callback(self, isPush: bool = False):\n        new_val = self.get_time()\n        changed = (\n            self._attr_native_value != new_val\n            or self._attr_available != self._server_state[\"available\"]\n        )\n        if changed:\n            _force = bool(not self._attr_available and self._server_state[\"available\"])\n            self._attr_native_value = new_val\n            if self._attr_available != self._server_state[\"available\"]:\n                if self._server_state[\"available\"]:\n                    self.update_option_listener()\n                else:\n                    self._attr_should_poll = True\n            self._attr_available = self._server_state[\"available\"]\n            self.async_schedule_update_ha_state(_force)\n\n    def update_option_listener(self):\n        _should_poll = self._code not in self._connector._ntfPropertyMap\n        self._attr_should_poll = (\n            self._connector._user_options.get(CONF_FORCE_POLLING, False) or _should_poll\n        )\n        self._attr_extra_state_attributes = {\"notify\": \"No\" if _should_poll else \"Yes\"}\n        _LOGGER.debug(\n            f\"{self._device_name}({self._code}): _should_poll is {_should_poll}\"\n        )\n"
  },
  {
    "path": "custom_components/echonetlite/translations/en.json",
    "content": "{\n    \"config\": {\n        \"abort\": {\n            \"already_configured\": \"Device is already configured\"\n        },\n        \"error\": {\n            \"cannot_connect\": \"Failed to connect.\",\n            \"invalid_auth\": \"Invalid authentication.\",\n            \"unknown\": \"Unexpected error.\",\n            \"not_found\": \"Device could not be detected.\",\n            \"already_configured\": \"That IP's Device is already configured.\",\n            \"cannot_get_property_maps\": \"Could not get the property maps required for operation.\"\n        },\n        \"step\": {\n            \"user\": {\n                \"title\": \"Device IP address and name setting (Auto Discovery)\",\n                \"description\": \"For automatic detection, after pressing the Submit button, please perform operations such as turning the device on or off within 30 seconds. If you know the IP address, enter the IP address and name.\",\n                \"data\": {\n                    \"host\": \"Host (IP Address)\",\n                    \"title\": \"Device Name\"\n                }\n            },\n            \"user_man\": {\n                \"title\": \"Device IP address and name setting\",\n                \"description\": \"\",\n                \"data\": {\n                    \"host\": \"Host (IP Address)\",\n                    \"title\": \"Device Name\"\n                }\n            }\n        },\n        \"progress\": {\n            \"discover\": \"Discovering ECHONETLite devices..\"\n        }\n    },\n    \"options\": {\n        \"error\": {\n            \"invalid_path\": \"The path provided is not valid. Should be in the format `user/repo-name` and should be a valid github repository.\"\n        },\n        \"step\": {\n            \"init\": {\n                \"title\": \"ECHONETLite Options\",\n                \"data\": {\n                    \"other_mode\": \"Handling of operation mode \\\"Other\\\"\",\n                    \"fan_settings\": \"Configure Fan settings\",\n                    \"swing_horiz\": \"Configure Horizontal Swing Settings\",\n                    \"swing_vert\": \"Configure Vertical Swing Settings\",\n                    \"auto_direction\": \"Configure Auto Direction\",\n                    \"swing_mode\": \"Configure Swing Mode\",\n                    \"ha_ui_swing\": \"Configure Swing Mode for Climate entity UI\",\n                    \"min_temp_heat\": \"Configure Minimum Temperature for Heating Operation\",\n                    \"max_temp_heat\": \"Configure Maximum Temperature for Heating Operation\",\n                    \"min_temp_cool\": \"Configure Minimum Temperature for Cooling Operation\",\n                    \"max_temp_cool\": \"Configure Maximum Temperature for Cooling Operation\",\n                    \"min_temp_auto\": \"Configure Minimum Temperature for Automatic Operation\",\n                    \"max_temp_auto\": \"Configure Maximum Temperature for Automatic Operation\",\n                    \"force_polling\": \"Do not stop polling even if immediate notification is expected\",\n                    \"super_energy\": \"Enable energy-related sensors (if available)\",\n                    \"batch_size_max\": \"Maximum number of properties for batch requests\"\n                },\n                \"description\": \"Configure optional settings\"\n            }\n        }\n    },\n    \"entity\": {\n        \"sensor\": {\n            \"echonetlite\": {\n                \"state\": {\n                    \"stopped\": \"Stopped\",\n                    \"supplying hot water\": \"Supplying Hot Water\",\n                    \"keeping bath temperature\": \"Keeping Bath Temperature\",\n                    \"heating\": \"Heating\",\n                    \"not heating\": \"Not Heating\"\n                }\n            }\n        },\n        \"select\": {\n            \"echonetlite\": {\n                \"state\": {\n                    \"auto\": \"Auto\",\n                    \"minimum\": \"Minimum\",\n                    \"low\": \"Low\",\n                    \"medium-low\": \"Medium-Low\",\n                    \"medium\": \"Medium\",\n                    \"medium-high\": \"Medium-High\",\n                    \"high\": \"High\",\n                    \"very-high\": \"Very-High\",\n                    \"max\": \"Max\",\n                    \"rc-right\": \"> >>\",\n                    \"left-lc\": \"<< <\",\n                    \"lc-center-rc\": \"< || >\",\n                    \"left-lc-rc-right\": \"<< < > >>\",\n                    \"right\": \">>\",\n                    \"rc\": \">\",\n                    \"center\": \"||\",\n                    \"center-right\": \"|| >>\",\n                    \"center-rc\": \"|| >\",\n                    \"center-rc-right\": \"|| > >>\",\n                    \"lc\": \"<\",\n                    \"lc-right\": \"< >>\",\n                    \"lc-rc\": \"< >\",\n                    \"lc-rc-right\": \"< > >>\",\n                    \"lc-center\": \"< ||\",\n                    \"lc-center-right\": \"< || >>\",\n                    \"lc-center-rc-right\": \"< || > >>\",\n                    \"left\": \"<<\",\n                    \"left-right\": \"<< >>\",\n                    \"left-rc\": \"<< >\",\n                    \"left-rc-right\": \"<< > >>\",\n                    \"left-center\": \"<< ||\",\n                    \"left-center-right\": \"<< || >>\",\n                    \"left-center-rc\": \"<< || >\",\n                    \"left-center-rc-right\": \"<< || > >>\",\n                    \"left-lc-right\": \"<< < >>\",\n                    \"left-lc-rc\": \"<< < >\",\n                    \"left-lc-center\": \"<< < ||\",\n                    \"left-lc-center-right\": \"<< < || >>\",\n                    \"left-lc-center-rc\": \"<< < || >\",\n                    \"left-lc-center-rc-right\": \"<< < || > >>\",\n                    \"upper\": \"Upper\",\n                    \"upper-central\": \"Upper Central\",\n                    \"central\": \"Central\",\n                    \"lower-central\": \"Lower Central\",\n                    \"lower\": \"Lower\",\n                    \"not-used\": \"Not Used (Off)\",\n                    \"vert\": \"Vertical\",\n                    \"horiz\": \"Horizontal\",\n                    \"vert-horiz\": \"Vertical-Horizontal\",\n                    \"non-auto\": \"Fixed\",\n                    \"auto-vert\": \"Auto Vertical\",\n                    \"auto-horiz\": \"Auto Horizontal\"\n                }\n            }\n        },\n        \"climate\": {\n            \"echonetlite\": {\n                \"state_attributes\": {\n                    \"fan_mode\": {\n                        \"state\": {\n                            \"auto\": \"Auto\",\n                            \"minimum\": \"Minimum\",\n                            \"low\": \"Low\",\n                            \"medium-low\": \"Medium-Low\",\n                            \"medium\": \"Medium\",\n                            \"medium-high\": \"Medium-High\",\n                            \"high\": \"High\",\n                            \"very-high\": \"Very-High\",\n                            \"max\": \"Max\"\n                        }\n                    },\n                    \"swing_mode\": {\n                        \"name\": \"Air direction\",\n                        \"state\": {\n                            \"rc-right\": \"> >>\",\n                            \"left-lc\": \"<< <\",\n                            \"lc-center-rc\": \"< || >\",\n                            \"left-lc-rc-right\": \"<< < > >>\",\n                            \"right\": \">>\",\n                            \"rc\": \">\",\n                            \"center\": \"||\",\n                            \"center-right\": \"|| >>\",\n                            \"center-rc\": \"|| >\",\n                            \"center-rc-right\": \"|| > >>\",\n                            \"lc\": \"<\",\n                            \"lc-right\": \"< >>\",\n                            \"lc-rc\": \"< >\",\n                            \"lc-rc-right\": \"< > >>\",\n                            \"lc-center\": \"< ||\",\n                            \"lc-center-right\": \"< || >>\",\n                            \"lc-center-rc-right\": \"< || > >>\",\n                            \"left\": \"<<\",\n                            \"left-right\": \"<< >>\",\n                            \"left-rc\": \"<< >\",\n                            \"left-rc-right\": \"<< > >>\",\n                            \"left-center\": \"<< ||\",\n                            \"left-center-right\": \"<< || >>\",\n                            \"left-center-rc\": \"<< || >\",\n                            \"left-center-rc-right\": \"<< || > >>\",\n                            \"left-lc-right\": \"<< < >>\",\n                            \"left-lc-rc\": \"<< < >\",\n                            \"left-lc-center\": \"<< < ||\",\n                            \"left-lc-center-right\": \"<< < || >>\",\n                            \"left-lc-center-rc\": \"<< < || >\",\n                            \"left-lc-center-rc-right\": \"<< < || > >>\",\n                            \"upper\": \"Upper\",\n                            \"upper-central\": \"Upper Central\",\n                            \"central\": \"Central\",\n                            \"lower-central\": \"Lower Central\",\n                            \"lower\": \"Lower\",\n                            \"not-used\": \"Not Used (Off)\",\n                            \"vert\": \"Vertical\",\n                            \"horiz\": \"Horizontal\",\n                            \"vert-horiz\": \"Vertical-Horizontal\",\n                            \"auto\": \"Auto\",\n                            \"non-auto\": \"Non-Auto\",\n                            \"auto-vert\": \"Auto Vertical\",\n                            \"auto-horiz\": \"Auto Horizontal\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"title\": \"ECHONETLite\"\n}"
  },
  {
    "path": "custom_components/echonetlite/translations/ja.json",
    "content": "{\n    \"config\": {\n        \"abort\": {\n            \"already_configured\": \"デバイスは構成済みです\"\n        },\n        \"error\": {\n            \"cannot_connect\": \"接続できません。\",\n            \"invalid_auth\": \"認証が無効です。\",\n            \"unknown\": \"予期しないエラーです。\",\n            \"not_found\": \"機器を検出できませんでした。\",\n            \"already_configured\": \"その IP のデバイスは既に構成されています。\",\n            \"cannot_get_property_maps\": \"操作に必要なプロパティマップの取得ができませんでした。\"\n        },\n        \"step\": {\n            \"user\": {\n                \"title\": \"機器IPアドレスと名称の設定 (自動検出)\",\n                \"description\": \"自動検出のため、送信ボタンを押してから30秒以内に電源のON/OFFなどの操作を行ってください。IPアドレスが分かっている場合は、IPアドレスと名称を入力し直してください。\",\n                \"data\": {\n                    \"host\": \"ホスト (IP アドレス)\",\n                    \"title\": \"機器名称\"\n                }\n            },\n            \"user_man\": {\n                \"title\": \"機器IPアドレスと名称の設定\",\n                \"description\": \"\",\n                \"data\": {\n                    \"host\": \"ホスト (IP アドレス)\",\n                    \"title\": \"機器名称\"\n                }\n            }\n        },\n        \"progress\": {\n            \"discover\": \"ECHONETLite 機器を探しています..\"\n        }\n    },\n    \"options\": {\n        \"error\": {\n            \"invalid_path\": \"指定されたパスが無効です。`ユーザー名/リポジトリ名` の形式の有効な GitHub リポジトリである必要があります。\"\n        },\n        \"step\": {\n            \"init\": {\n                \"title\": \"ECHONET Lite オプション\",\n                \"data\": {\n                    \"other_mode\": \"運転モード「その他」の取り扱い\",\n                    \"fan_settings\": \"風量設定の構成\",\n                    \"swing_horiz\": \"風向左右設定の構成\",\n                    \"swing_vert\": \"風向上下設定の構成\",\n                    \"auto_direction\": \"自動風向の構成\",\n                    \"swing_mode\": \"スイングモードの構成\",\n                    \"ha_ui_swing\": \"エアコンエンティティUIでのスイングモードの構成\",\n                    \"min_temp_heat\": \"暖房時の最低温度設定\",\n                    \"max_temp_heat\": \"暖房時の最高温度設定\",\n                    \"min_temp_cool\": \"冷房時の最低温度設定\",\n                    \"max_temp_cool\": \"冷房時の最高温度設定\",\n                    \"min_temp_auto\": \"自動モード時の最低温度設定\",\n                    \"max_temp_auto\": \"自動モード時の最高温度設定\",\n                    \"force_polling\": \"即時通知が見込める場合でもポーリングを止めない\",\n                    \"super_energy\": \"エネルギー関連のセンサーを有効にする(取得可能な場合)\",\n                    \"batch_size_max\": \"バッチリクエストの最大プロパティ数\"\n                },\n                \"description\": \"オプション設定を構成する\"\n            }\n        }\n    },\n    \"entity\": {\n        \"sensor\": {\n            \"echonetlite\": {\n                \"state\": {\n                    \"Stopped\": \"停止\",\n                    \"Supplying Hot Water\": \"湯はり中\",\n                    \"Keeping Bath Temperature\": \"保温中\",\n                    \"heating\": \"加温中\",\n                    \"not heating\": \"停止\"\n                }\n            }\n        },\n        \"select\": {\n            \"echonetlite\": {\n                \"state\": {\n                    \"auto\": \"自動\",\n                    \"minimum\": \"微風\",\n                    \"low\": \"弱\",\n                    \"medium-low\": \"中弱\",\n                    \"medium\": \"中\",\n                    \"medium-high\": \"中強\",\n                    \"high\": \"強\",\n                    \"very-high\": \"強々\",\n                    \"max\": \"最大\",\n                    \"rc-right\": \"> >>\",\n                    \"left-lc\": \"<< <\",\n                    \"lc-center-rc\": \"< || >\",\n                    \"left-lc-rc-right\": \"<< < > >>\",\n                    \"right\": \">>\",\n                    \"rc\": \">\",\n                    \"center\": \"||\",\n                    \"center-right\": \"|| >>\",\n                    \"center-rc\": \"|| >\",\n                    \"center-rc-right\": \"|| > >>\",\n                    \"lc\": \"<\",\n                    \"lc-right\": \"< >>\",\n                    \"lc-rc\": \"< >\",\n                    \"lc-rc-right\": \"< > >>\",\n                    \"lc-center\": \"< ||\",\n                    \"lc-center-right\": \"< || >>\",\n                    \"lc-center-rc-right\": \"< || > >>\",\n                    \"left\": \"<<\",\n                    \"left-right\": \"<< >>\",\n                    \"left-rc\": \"<< >\",\n                    \"left-rc-right\": \"<< > >>\",\n                    \"left-center\": \"<< ||\",\n                    \"left-center-right\": \"<< || >>\",\n                    \"left-center-rc\": \"<< || >\",\n                    \"left-center-rc-right\": \"<< || > >>\",\n                    \"left-lc-right\": \"<< < >>\",\n                    \"left-lc-rc\": \"<< < >\",\n                    \"left-lc-center\": \"<< < ||\",\n                    \"left-lc-center-right\": \"<< < || >>\",\n                    \"left-lc-center-rc\": \"<< < || >\",\n                    \"left-lc-center-rc-right\": \"<< < || > >>\",\n                    \"upper\": \"上\",\n                    \"upper-central\": \"中上\",\n                    \"central\": \"中\",\n                    \"lower-central\": \"中下\",\n                    \"lower\": \"下\",\n                    \"not-used\": \"スイングなし\",\n                    \"vert\": \"上下スイング\",\n                    \"horiz\": \"左右スイング\",\n                    \"vert-horiz\": \"上下左右スイング\",\n                    \"non-auto\": \"固定\",\n                    \"auto-vert\": \"上下自動\",\n                    \"auto-horiz\": \"左右自動\"\n                }\n            }\n        },\n        \"climate\": {\n            \"echonetlite\": {\n                \"state_attributes\": {\n                    \"fan_mode\": {\n                        \"state\": {\n                            \"auto\": \"自動\",\n                            \"minimum\": \"微風\",\n                            \"low\": \"弱\",\n                            \"medium-low\": \"中弱\",\n                            \"medium\": \"中\",\n                            \"medium-high\": \"中強\",\n                            \"high\": \"強\",\n                            \"very-high\": \"強々\",\n                            \"max\": \"最大\"\n                        }\n                    },\n                    \"swing_mode\": {\n                        \"name\": \"風向設定\",\n                        \"state\": {\n                            \"rc-right\": \"> >>\",\n                            \"left-lc\": \"<< <\",\n                            \"lc-center-rc\": \"< || >\",\n                            \"left-lc-rc-right\": \"<< < > >>\",\n                            \"right\": \">>\",\n                            \"rc\": \">\",\n                            \"center\": \"||\",\n                            \"center-right\": \"|| >>\",\n                            \"center-rc\": \"|| >\",\n                            \"center-rc-right\": \"|| > >>\",\n                            \"lc\": \"<\",\n                            \"lc-right\": \"< >>\",\n                            \"lc-rc\": \"< >\",\n                            \"lc-rc-right\": \"< > >>\",\n                            \"lc-center\": \"< ||\",\n                            \"lc-center-right\": \"< || >>\",\n                            \"lc-center-rc-right\": \"< || > >>\",\n                            \"left\": \"<<\",\n                            \"left-right\": \"<< >>\",\n                            \"left-rc\": \"<< >\",\n                            \"left-rc-right\": \"<< > >>\",\n                            \"left-center\": \"<< ||\",\n                            \"left-center-right\": \"<< || >>\",\n                            \"left-center-rc\": \"<< || >\",\n                            \"left-center-rc-right\": \"<< || > >>\",\n                            \"left-lc-right\": \"<< < >>\",\n                            \"left-lc-rc\": \"<< < >\",\n                            \"left-lc-center\": \"<< < ||\",\n                            \"left-lc-center-right\": \"<< < || >>\",\n                            \"left-lc-center-rc\": \"<< < || >\",\n                            \"left-lc-center-rc-right\": \"<< < || > >>\",\n                            \"upper\": \"上\",\n                            \"upper-central\": \"中上\",\n                            \"central\": \"中\",\n                            \"lower-central\": \"中下\",\n                            \"lower\": \"下\",\n                            \"not-used\": \"スイングなし\",\n                            \"vert\": \"上下スイング\",\n                            \"horiz\": \"左右スイング\",\n                            \"vert-horiz\": \"上下左右スイング\",\n                            \"auto\": \"自動\",\n                            \"non-auto\": \"手動\",\n                            \"auto-vert\": \"上下自動\",\n                            \"auto-horiz\": \"左右自動\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"title\": \"ECHONETLite\"\n}"
  },
  {
    "path": "custom_components/echonetlite/translations/pt.json",
    "content": "{\n  \"config\": {\n    \"abort\": {\n      \"already_configured\": \"Dispositivo já configurado\"\n    },\n    \"error\": {\n      \"cannot_connect\": \"Falha ao ligar.\",\n      \"invalid_auth\": \"Autenticação inválida.\",\n      \"unknown\": \"Erro inesperado.\",\n      \"not_found\": \"Não foi possível detetar o dispositivo.\",\n      \"already_configured\": \"O dispositivo com esse IP já está configurado.\",\n      \"cannot_get_property_maps\": \"Não foi possível obter os mapas de propriedades necessários para a operação.\"\n    },\n    \"step\": {\n      \"user\": {\n        \"title\": \"Definição de endereço IP e nome do dispositivo (Descoberta Automática)\",\n        \"description\": \"Para deteção automática, após pressionar o botão Enviar, efetue operações como ligar ou desligar o dispositivo dentro de 30 segundos. Se souber o endereço IP, introduza o endereço IP e o nome.\",\n        \"data\": {\n          \"host\": \"Anfitrião (Endereço IP)\",\n          \"title\": \"Nome do Dispositivo\"\n        }\n      },\n      \"user_man\": {\n        \"title\": \"Definição de endereço IP e nome do dispositivo\",\n        \"description\": \"\",\n        \"data\": {\n          \"host\": \"Anfitrião (Endereço IP)\",\n          \"title\": \"Nome do Dispositivo\"\n        }\n      }\n    },\n    \"progress\": {\n      \"discover\": \"A descobrir dispositivos ECHONETLite..\"\n    }\n  },\n  \"options\": {\n    \"error\": {\n      \"invalid_path\": \"O caminho fornecido não é válido. Deve estar no formato `utilizador/nome-repo` e deve ser um repositório github válido.\"\n    },\n    \"step\": {\n      \"init\": {\n        \"title\": \"Opções ECHONETLite\",\n        \"data\": {\n          \"other_mode\": \"Tratamento do modo de operação \\\"Outro\\\"\",\n          \"fan_settings\": \"Configurar definições da ventoinha\",\n          \"swing_horiz\": \"Configurar definições de Oscilação Horizontal\",\n          \"swing_vert\": \"Configurar definições de Oscilação Vertical\",\n          \"auto_direction\": \"Configurar Direção Automática\",\n          \"swing_mode\": \"Configurar Modo de Oscilação\",\n          \"ha_ui_swing\": \"Configurar Modo de Oscilação para a interface de utilizador da entidade Clima\",\n          \"min_temp_heat\": \"Configurar Temperatura Mínima para Operação de Aquecimento\",\n          \"max_temp_heat\": \"Configurar Temperatura Máxima para Operação de Aquecimento\",\n          \"min_temp_cool\": \"Configurar Temperatura Mínima para Operação de Arrefecimento\",\n          \"max_temp_cool\": \"Configurar Temperatura Máxima para Operação de Arrefecimento\",\n          \"min_temp_auto\": \"Configurar Temperatura Mínima para Operação Automática\",\n          \"max_temp_auto\": \"Configurar Temperatura Máxima para Operação Automática\",\n          \"force_polling\": \"Não parar o polling mesmo que a notificação imediata seja esperada\",\n          \"super_energy\": \"Ativar sensores relacionados com energia (se disponíveis)\",\n          \"batch_size_max\": \"Número máximo de propriedades para pedidos em lote\"\n        },\n        \"description\": \"Configurar definições opcionais\"\n      }\n    }\n  },\n  \"entity\": {\n    \"sensor\": {\n      \"echonetlite\": {\n        \"state\": {\n          \"stopped\": \"Parado\",\n          \"supplying hot water\": \"A fornecer água quente\",\n          \"keeping bath temperature\": \"A manter a temperatura do banho\",\n          \"heating\": \"A aquecer\",\n          \"not heating\": \"Não está a aquecer\"\n        }\n      }\n    },\n    \"select\": {\n      \"echonetlite\": {\n        \"state\": {\n          \"auto\": \"Automático\",\n          \"minimum\": \"Mínimo\",\n          \"low\": \"Baixo\",\n          \"medium-low\": \"Médio-Baixo\",\n          \"medium\": \"Médio\",\n          \"medium-high\": \"Médio-Alto\",\n          \"high\": \"Alto\",\n          \"very-high\": \"Muito Alto\",\n          \"max\": \"Máximo\",\n          \"rc-right\": \"> >>\",\n          \"left-lc\": \"<< <\",\n          \"lc-center-rc\": \"< || >\",\n          \"left-lc-rc-right\": \"<< < > >>\",\n          \"right\": \">>\",\n          \"rc\": \">\",\n          \"center\": \"||\",\n          \"center-right\": \"|| >>\",\n          \"center-rc\": \"|| >\",\n          \"center-rc-right\": \"|| > >>\",\n          \"lc\": \"<\",\n          \"lc-right\": \"< >>\",\n          \"lc-rc\": \"< >\",\n          \"lc-rc-right\": \"< > >>\",\n          \"lc-center\": \"< ||\",\n          \"lc-center-right\": \"< || >>\",\n          \"lc-center-rc-right\": \"< || > >>\",\n          \"left\": \"<<\",\n          \"left-right\": \"<< >>\",\n          \"left-rc\": \"<< >\",\n          \"left-rc-right\": \"<< > >>\",\n          \"left-center\": \"<< ||\",\n          \"left-center-right\": \"<< || >>\",\n          \"left-center-rc\": \"<< || >\",\n          \"left-center-rc-right\": \"<< || > >>\",\n          \"left-lc-right\": \"<< < >>\",\n          \"left-lc-rc\": \"<< < >\",\n          \"left-lc-center\": \"<< < ||\",\n          \"left-lc-center-right\": \"<< < || >>\",\n          \"left-lc-center-rc\": \"<< < || >\",\n          \"left-lc-center-rc-right\": \"<< < || > >>\",\n          \"upper\": \"Superior\",\n          \"upper-central\": \"Superior Central\",\n          \"central\": \"Central\",\n          \"lower-central\": \"Inferior Central\",\n          \"lower\": \"Inferior\",\n          \"not-used\": \"Não Usado (Desligado)\",\n          \"vert\": \"Vertical\",\n          \"horiz\": \"Horizontal\",\n          \"vert-horiz\": \"Vertical-Horizontal\",\n          \"non-auto\": \"Fixo\",\n          \"auto-vert\": \"Automático Vertical\",\n          \"auto-horiz\": \"Automático Horizontal\"\n        }\n      }\n    },\n    \"climate\": {\n      \"echonetlite\": {\n        \"state_attributes\": {\n          \"fan_mode\": {\n            \"state\": {\n              \"auto\": \"Automático\",\n              \"minimum\": \"Mínimo\",\n              \"low\": \"Baixo\",\n              \"medium-low\": \"Médio-Baixo\",\n              \"medium\": \"Médio\",\n              \"medium-high\": \"Médio-Alto\",\n              \"high\": \"Alto\",\n              \"very-high\": \"Muito Alto\",\n              \"max\": \"Máximo\"\n            }\n          },\n          \"swing_mode\": {\n            \"name\": \"Direção do ar\",\n            \"state\": {\n              \"rc-right\": \"> >>\",\n              \"left-lc\": \"<< <\",\n              \"lc-center-rc\": \"< || >\",\n              \"left-lc-rc-right\": \"<< < > >>\",\n              \"right\": \">>\",\n              \"rc\": \">\",\n              \"center\": \"||\",\n              \"center-right\": \"|| >>\",\n              \"center-rc\": \"|| >\",\n              \"center-rc-right\": \"|| > >>\",\n              \"lc\": \"<\",\n              \"lc-right\": \"< >>\",\n              \"lc-rc\": \"< >\",\n              \"lc-rc-right\": \"< > >>\",\n              \"lc-center\": \"< ||\",\n              \"lc-center-right\": \"< || >>\",\n              \"lc-center-rc-right\": \"< || > >>\",\n              \"left\": \"<<\",\n              \"left-right\": \"<< >>\",\n              \"left-rc\": \"<< >\",\n              \"left-rc-right\": \"<< > >>\",\n              \"left-center\": \"<< ||\",\n              \"left-center-right\": \"<< || >>\",\n              \"left-center-rc\": \"<< || >\",\n              \"left-center-rc-right\": \"<< || > >>\",\n              \"left-lc-right\": \"<< < >>\",\n              \"left-lc-rc\": \"<< < >\",\n              \"left-lc-center\": \"<< < ||\",\n              \"left-lc-center-right\": \"<< < || >>\",\n              \"left-lc-center-rc\": \"<< < || >\",\n              \"left-lc-center-rc-right\": \"<< < || > >>\",\n"
  },
  {
    "path": "hacs.json",
    "content": "{\n  \"name\": \"ECHONETLite Platform\",\n  \"render_readme\": true,\n  \"domains\": [\"climate\", \"sensor\", \"select\", \"light\", \"fan\", \"switch\", \"time\"],\n  \"homeassistant\": \"2024.1.0\",\n  \"iot_class\": [\"Local Push\", \"Local Polling\"]\n}\n"
  },
  {
    "path": "info.md",
    "content": "[![GitHub Release][releases-shield]][releases]\n[![License][license-shield]](LICENSE)\n[![hacs][hacsbadge]][hacs]\n![Project Maintenance][maintenance-shield]\n\n# ECHONETLite Platform for Home Assistant\n\n_Component to integrate ECHONETLite compatable HVAC systems using the [pychonet][pychonet] library._\n\n**This component will set up the following platforms.**\n\nPlatform | Description\n-- | --\n`climate` | Interface to ECHONETLite API to control your ECHONETLite compatible HVAC (Commonly found in Asia-Pacific regions)\n`sensor`  | Interface to ECHONETLite API to poll indoor and outdoor temperature sensors.\n`select`  | Interface to ECHONETLite API to provide drop down menus for swing modes.\n`light`   | Interface to ECHONETLite API to provide light functionality for supported devices.\n`light`   | Interface to ECHONETLite API to provide fan functionality for supported devices.\n\n![example][exampleimg]\n\n{% if not installed %}\n## Pre-installation - if previously installed versions prior to 3.0.1\n1. Delete 'mitsubishi' from your 'custom_components' directory\n2. Remove references to 'mitsubishi' from 'configuration.yaml'\n\n## Installation\n1. Click install and then reload Home Assistant.\n2. Platform 'echonetlite' should be added to 'custom_components' directory\n3. You may also need to clear your browser cache.\n4. Go to configuration -> integrations -> ADD INTEGRATION.\n5. Select the 'echonetlite' integration. Enter your IP address in the host field, and give the platform a name.\n6. Platform should automatically configure 'climate' and depending on your system will configure 'sensor' and 'select'\n6. If you have additional HVACs then repeat step 4.\n\n{% endif %}\n\n# Current working systems:\nBased upon feedback this custom component works with the following\ncompatible ECHONETLite Devices:\n\n* Mitsubishi MAC-568IF-E WiFi Adaptor connected to the following systems:\n  * GE Series\n     * MSZ-GE42VAD\n     * MSZ-GE24VAD\n     * MSZ-GL71VGD\n     * MSZ-GL50VGD\n     * MSZ-GL35VGD\n     * MSZ-GL25VGD\n  * AP Series\n     * MSZ-AP22VGD\n     * MSZ-AP25VGD\n     * MSZ-AP50VGD\n  * Ducted\n     * PEA-M100GAA\n     * PEA-RP140GAA\n\n* Mitsubishi HM-W002-AC WiFi Adaptor connected to the following systems:\n  * JXV Series\n     * MSZ-JXV4018S\n\n* 'MoekadenRoom' ECHONETLite Simulator: https://github.com/SonyCSL/MoekadenRoom\n     * Generic HVAC Climate\n     * Light Sensor\n     * Lock Sensor\n     * Temperature Sensor\n\n* Sharp\n     * Air Conditioners\n         * AY-J22H\n         * AY-L40P\n     * Air Purifier\n         * KI-HS70\n\n* Daikin (ECHONETLite enabled models)\n* Koizumi\n     * Lighting system AE50264E bridge (https://www.koizumi-lt.co.jp/product/jyutaku/tree/ )\n\n## Mitsubishi MAC-568IF-E\nFrom the official Mitsubishi AU/NZ Wifi App, you will need to enable\nthe 'ECHONET lite' protocol under the 'edit unit' settings.\n\n## Support for Other ECHONETLite devices\nAt present this platform is somewhat hard coded to HVACs but can be modified for other uses as needed.\nIf you have ECHONETLite devices that are not HVACs and you would like to use them\nvia this integration then please raise an issue or better yet a PR.\n\n![echonet][echonetimg]\n\n***\n[pychonet]: https://github.com/scottyphillips/pychonet\n[echonetlite_homeassistant]: https://github.com/scottyphillips/echonetlite_homeassistant\n[hacs]: https://github.com/custom-components/hacs\n[hacsbadge]: https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge\n[releases-shield]: https://img.shields.io/github/release/scottyphillips/echonetlite_homeassistant.svg?style=for-the-badge\n[releases]: https://github.com/scottyphillips/echonetlite_homeassistant/releases\n[license-shield]:https://img.shields.io/github/license/scottyphillips/echonetlite_homeassistant?style=for-the-badge\n[buymecoffee]: https://www.buymeacoffee.com/RgKWqyt?style=for-the-badge\n[buymecoffeebadge]: https://img.shields.io/badge/buy%20me%20a%20coffee-donate-yellow.svg?style=for-the-badge\n[maintenance-shield]: https://img.shields.io/badge/Maintainer-Scott%20Phillips-blue?style=for-the-badge\n[exampleimg]: https://raw.githubusercontent.com/scottyphillips/echonetlite_homeassistant/master/Mitsubishi.jpg\n[echonetimg]: https://raw.githubusercontent.com/scottyphillips/echonetlite_homeassistant/master/ECHONET.jpeg\n"
  }
]