├── .gitignore ├── .flake8 ├── docs └── media │ ├── router.png │ ├── sensors.png │ ├── switches.png │ └── config_flow.png ├── hacs.json ├── .github ├── workflows │ ├── codechecker.yml │ ├── hassfest.yaml │ ├── hacs.yaml │ └── release.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── custom_components └── tplink_router │ ├── services.yaml │ ├── const.py │ ├── manifest.json │ ├── translations │ ├── ja.json │ ├── cs.json │ ├── es.json │ ├── bg.json │ ├── it.json │ ├── pt.json │ ├── en.json │ ├── sv.json │ ├── tr.json │ ├── sk.json │ ├── nl.json │ └── fr.json │ ├── strings.json │ ├── button.py │ ├── config_flow.py │ ├── coordinator.py │ ├── __init__.py │ ├── device_tracker.py │ ├── switch.py │ └── sensor.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .git 2 | venv 3 | .idea 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | 3 | max-line-length = 120 4 | exclude = .git,.github,docs,venv 5 | -------------------------------------------------------------------------------- /docs/media/router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrErohin/home-assistant-tplink-router/HEAD/docs/media/router.png -------------------------------------------------------------------------------- /docs/media/sensors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrErohin/home-assistant-tplink-router/HEAD/docs/media/sensors.png -------------------------------------------------------------------------------- /docs/media/switches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrErohin/home-assistant-tplink-router/HEAD/docs/media/switches.png -------------------------------------------------------------------------------- /docs/media/config_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexandrErohin/home-assistant-tplink-router/HEAD/docs/media/config_flow.png -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TP-Link Router", 3 | "filename": "tplink_router.zip", 4 | "homeassistant": "2023.9.3", 5 | "hide_default_branch": true, 6 | "render_readme": true, 7 | "zip_release": true 8 | } -------------------------------------------------------------------------------- /.github/workflows/codechecker.yml: -------------------------------------------------------------------------------- 1 | name: Code Checker 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Run flake8 13 | uses: py-actions/flake8@v2 -------------------------------------------------------------------------------- /custom_components/tplink_router/services.yaml: -------------------------------------------------------------------------------- 1 | send_sms: 2 | fields: 3 | number: 4 | required: true 5 | selector: 6 | text: 7 | text: 8 | required: true 9 | selector: 10 | text: 11 | device: 12 | required: true 13 | selector: 14 | device: -------------------------------------------------------------------------------- /custom_components/tplink_router/const.py: -------------------------------------------------------------------------------- 1 | DEFAULT_NAME = "TP-Link Router" 2 | DOMAIN = "tplink_router" 3 | DEFAULT_USER = "admin" 4 | DEFAULT_HOST = "http://192.168.0.1" 5 | 6 | EVENT_NEW_DEVICE = f"{DOMAIN}_new_device" 7 | EVENT_ONLINE = f"{DOMAIN}_device_online" 8 | EVENT_OFFLINE = f"{DOMAIN}_device_offline" 9 | EVENT_NEW_SMS = f"{DOMAIN}_new_sms" 10 | -------------------------------------------------------------------------------- /custom_components/tplink_router/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "tplink_router", 3 | "name": "TP-Link Router", 4 | "codeowners": ["@AlexandrErohin"], 5 | "config_flow": true, 6 | "documentation": "https://github.com/AlexandrErohin/home-assistant-tplink-router", 7 | "iot_class": "local_polling", 8 | "issue_tracker": "https://github.com/AlexandrErohin/home-assistant-tplink-router/issues", 9 | "requirements": ["tplinkrouterc6u==5.12.1"], 10 | "version": "2.14.1" 11 | } -------------------------------------------------------------------------------- /.github/workflows/hassfest.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | name: Validate with hassfest 4 | 5 | on: 6 | push: 7 | pull_request: 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - uses: actions/checkout@v4 14 | name: Download repo 15 | with: 16 | fetch-depth: 0 17 | - uses: actions/setup-python@v4 18 | name: Setup Python 19 | with: 20 | python-version: '3.10.x' 21 | - uses: "actions/checkout@v4" 22 | - uses: home-assistant/actions/hassfest@master 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Logs** 14 | Add also all relevant logs from **Settings->System->Logs**. 15 | 16 | **Additional Information (please complete the following information)** 17 | - Router Hardware Version: 18 | - Router Firmware Version: 19 | - Home Assistant Version: 20 | - TP-Link Router Integration Component Version: 21 | -------------------------------------------------------------------------------- /.github/workflows/hacs.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | name: Validate HACS 4 | on: 5 | push: 6 | pull_request: 7 | jobs: 8 | ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | name: Download repo 13 | with: 14 | fetch-depth: 0 15 | 16 | - uses: actions/setup-python@v4 17 | name: Setup Python 18 | with: 19 | python-version: '3.10.x' 20 | 21 | - uses: actions/cache@v4 22 | name: Cache 23 | with: 24 | path: | 25 | ~/.cache/pip 26 | key: custom-component-ci 27 | 28 | - name: HACS Action 29 | uses: hacs/action@main 30 | with: 31 | CATEGORY: integration 32 | -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "統合の設定", 6 | "data": { 7 | "host": "ホスト", 8 | "username": "ログイン", 9 | "password": "パスワード", 10 | "scan_interval": "更新のスキャン間隔(秒)", 11 | "verify_ssl": "https接続でSSLを検証する" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "ルーターのパラメーターを編集します。", 20 | "data": { 21 | "host": "ホスト", 22 | "username": "ログイン", 23 | "password": "パスワード", 24 | "scan_interval": "更新のスキャン間隔(秒)", 25 | "verify_ssl": "https接続でSSLを検証する" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/cs.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Nastavení integrace", 6 | "data": { 7 | "host": "Host", 8 | "username": "Login", 9 | "password": "Heslo", 10 | "scan_interval": "Interval skenování v sekundách", 11 | "verify_ssl": "Kontrolovat ssl pro HTTPS připojení" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Upravit parametry nastavení.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Login", 23 | "password": "Heslo", 24 | "scan_interval": "Interval skenování v sekundách", 25 | "verify_ssl": "Kontrolovat ssl pro HTTPS připojení" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Configuración de la integración", 6 | "data": { 7 | "host": "Host", 8 | "username": "Usuario", 9 | "password": "Contraseña", 10 | "scan_interval": "Intervalo de escaneo para actualizaciones en segundos", 11 | "verify_ssl": "Verificar SSL para conexión https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Editar parámetros del router.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Usuario", 23 | "password": "Contraseña", 24 | "scan_interval": "Intervalo de escaneo para actualizaciones en segundos", 25 | "verify_ssl": "Verificar SSL para conexión https" 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/bg.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Конфигурация на интеграцията", 6 | "data": { 7 | "host": "Хост", 8 | "username": "Потребителско име", 9 | "password": "Парола", 10 | "scan_interval": "Интервал за сканиране за актуализации в секунди", 11 | "verify_ssl": "Проверка на SSL за HTTPS връзка" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Редактиране на параметрите за рутера.", 20 | "data": { 21 | "host": "Хост", 22 | "username": "Потребителско име", 23 | "password": "Парола", 24 | "scan_interval": "Интервал за сканиране за актуализации в секунди", 25 | "verify_ssl": "Проверка на SSL за HTTPS връзка" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Configurazione dell'integrazione", 6 | "data": { 7 | "host": "Host", 8 | "username": "Nome utente", 9 | "password": "Password", 10 | "scan_interval": "Intervallo di scansione per aggiornamenti in secondi", 11 | "verify_ssl": "Verifica SSL per la connessione https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Modifica parametri del router", 20 | "data": { 21 | "host": "Host", 22 | "username": "Nome utente", 23 | "password": "Password", 24 | "scan_interval": "Intervallo di scansione per aggiornamenti in secondi", 25 | "verify_ssl": "Verifica SSL per la connessione https" 26 | } 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Configuração da integração", 6 | "data": { 7 | "host": "Host", 8 | "username": "Utilizador", 9 | "password": "Palavra-passe", 10 | "scan_interval": "Intervalo de verificação para atualizações em segundos", 11 | "verify_ssl": "Verificar SSL para a conexão https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Editar parâmetros do router.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Utilizador", 23 | "password": "Palavra-passe", 24 | "scan_interval": "Intervalo de verificação para atualizações em segundos", 25 | "verify_ssl": "Verificar SSL para a conexão https" 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Release" 2 | 3 | on: 4 | release: 5 | types: 6 | - "published" 7 | 8 | permissions: {} 9 | 10 | jobs: 11 | release: 12 | name: "Release" 13 | runs-on: "ubuntu-latest" 14 | permissions: 15 | contents: write 16 | steps: 17 | - name: "Checkout the repository" 18 | uses: "actions/checkout@v4" 19 | 20 | - name: "Adjust version number" 21 | shell: "bash" 22 | run: | 23 | yq -i -o json '.version="${{ github.event.release.tag_name }}"' \ 24 | "${{ github.workspace }}/custom_components/tplink_router/manifest.json" 25 | 26 | - name: "ZIP the integration directory" 27 | shell: "bash" 28 | run: | 29 | cd "${{ github.workspace }}/custom_components/tplink_router" 30 | zip tplink_router.zip -r ./ 31 | 32 | - name: "Upload the ZIP file to the release" 33 | uses: softprops/action-gh-release@v2 34 | with: 35 | files: ${{ github.workspace }}/custom_components/tplink_router/tplink_router.zip 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 AlexandrErohin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Integration config", 6 | "data": { 7 | "host": "Host", 8 | "username": "Login", 9 | "password": "Password", 10 | "scan_interval": "Scan interval for updates in seconds", 11 | "verify_ssl": "Verify ssl for https connection" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Edit parameters for the router.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Login", 23 | "password": "Password", 24 | "scan_interval": "Scan interval for updates in seconds", 25 | "verify_ssl": "Verify ssl for https connection" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Send SMS", 33 | "description": "Send SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Phone", 37 | "description": "Phone number" 38 | }, 39 | "text": { 40 | "name": "Message", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Select the TP-Link router to send sms" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/sv.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Integrationskonfiguration", 6 | "data": { 7 | "host": "Host", 8 | "username": "Login", 9 | "password": "Lösenord", 10 | "scan_interval": "Uppdateringsintervall i sekunder", 11 | "verify_ssl": "Verifiera ssl för https anslutning" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Ändra konfiguration för routern.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Login", 23 | "password": "Lösenord", 24 | "scan_interval": "Uppdateringsintervall i sekunder", 25 | "verify_ssl": "Verifiera ssl för https anslutning" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Skicka SMS", 33 | "description": "Skicka SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Telefon", 37 | "description": "Telefonnummer" 38 | }, 39 | "text": { 40 | "name": "Meddelande", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Välj TP-Link router för att skicka sms" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/tr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Entegrasyon ayarları", 6 | "data": { 7 | "host": "Host", 8 | "username": "Kullanıcı Adı", 9 | "password": "Parola", 10 | "scan_interval": "Güncellemeler için tarama sıklığı (saniye)", 11 | "verify_ssl": "HTTPS için SSL doğrula" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Router için parametreleri düzenle.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Kullanıcı Adı", 23 | "password": "Parola", 24 | "scan_interval": "Güncellemeler için tarama sıklığı (saniye)", 25 | "verify_ssl": "HTTPS için SSL doğrula" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "SMS gönder", 33 | "description": "SMS gönder", 34 | "fields": { 35 | "number": { 36 | "name": "Telefon", 37 | "description": "Telefon numarası" 38 | }, 39 | "text": { 40 | "name": "Mesaj", 41 | "description": "Metin" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "SMS göndermek için TP-Link Router seçin" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Nastavenie integrácie", 6 | "data": { 7 | "host": "Hostiteľ", 8 | "username": "Meno", 9 | "password": "Heslo", 10 | "scan_interval": "Interval skenovania aktualizácií v sekundách", 11 | "verify_ssl": "Overte ssl pre pripojenie https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Upravte parametre smerovača.", 20 | "data": { 21 | "host": "Hostiteľ", 22 | "username": "Meno", 23 | "password": "Heslo", 24 | "scan_interval": "Interval skenovania aktualizácií v sekundách", 25 | "verify_ssl": "Overte ssl pre pripojenie https" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Pošlite SMS", 33 | "description": "Pošlite SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Phone", 37 | "description": "Telefónne číslo" 38 | }, 39 | "text": { 40 | "name": "Správa", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Smerovač", 45 | "description": "Vyberte smerovač TP-Link na odosielanie SMS" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Integratie configuratie", 6 | "data": { 7 | "host": "Host", 8 | "username": "Gebruikersnaam", 9 | "password": "Wachtwoord", 10 | "scan_interval": "Scaninterval voor updates in seconden", 11 | "verify_ssl": "Verifieer SSL voor https-verbinding" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Parameters bewerken voor de router.", 20 | "data": { 21 | "host": "Host", 22 | "username": "Gebruikersnaam", 23 | "password": "Wachtwoord", 24 | "scan_interval": "Scaninterval voor updates in seconden", 25 | "verify_ssl": "Verifieer SSL voor https-verbinding" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Verzend SMS", 33 | "description": "Een SMS verzenden", 34 | "fields": { 35 | "number": { 36 | "name": "Telefoon", 37 | "description": "Telefoonnummer" 38 | }, 39 | "text": { 40 | "name": "Bericht", 41 | "description": "Tekst" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Selecteer de TP-Link-router om een sms te verzenden" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /custom_components/tplink_router/translations/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "step": { 4 | "user": { 5 | "description": "Configuration de l'intégration", 6 | "data": { 7 | "host": "Hôte", 8 | "username": "Utilisateur", 9 | "password": "Mot de passe", 10 | "scan_interval": "Intervalle d'analyse des mises à jour en secondes", 11 | "verify_ssl": "Vérifier SSL pour la connexion https" 12 | } 13 | } 14 | } 15 | }, 16 | "options": { 17 | "step": { 18 | "init": { 19 | "description": "Modifier les paramètres du routeur.", 20 | "data": { 21 | "host": "Hôte", 22 | "username": "Utilisateur", 23 | "password": "Mot de passe", 24 | "scan_interval": "Intervalle d'analyse des mises à jour en secondes", 25 | "verify_ssl": "Vérifier SSL pour la connexion https" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Envoi SMS", 33 | "description": "Envoi SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Téléphone", 37 | "description": "Numéro de téléphone" 38 | }, 39 | "text": { 40 | "name": "Message", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Select the TP-Link router to send sms" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /custom_components/tplink_router/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "TP-Link Router", 3 | "config": { 4 | "step": { 5 | "user": { 6 | "description": "description", 7 | "data": { 8 | "host": "[%key:common::config_flow::data::host%]", 9 | "username": "[%key:common::config_flow::data::username%]", 10 | "password": "[%key:common::config_flow::data::password%]", 11 | "scan_interval": "[%key:common::config_flow::data::scan_interval%]", 12 | "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" 13 | } 14 | } 15 | } 16 | }, 17 | "options": { 18 | "step": { 19 | "init": { 20 | "data": { 21 | "host": "[%key:common::config_flow::data::host%]", 22 | "username": "[%key:common::config_flow::data::username%]", 23 | "password": "[%key:common::config_flow::data::password%]", 24 | "scan_interval": "[%key:common::config_flow::data::scan_interval%]", 25 | "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" 26 | } 27 | } 28 | } 29 | }, 30 | "services": { 31 | "send_sms": { 32 | "name": "Send SMS", 33 | "description": "Send SMS", 34 | "fields": { 35 | "number": { 36 | "name": "Phone", 37 | "description": "Phone number" 38 | }, 39 | "text": { 40 | "name": "Message", 41 | "description": "Text" 42 | }, 43 | "device": { 44 | "name": "Router", 45 | "description": "Select the TP-Link router to send sms" 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /custom_components/tplink_router/button.py: -------------------------------------------------------------------------------- 1 | """Component providing support for TPLinkRouter button entities.""" 2 | from __future__ import annotations 3 | 4 | from collections.abc import Callable 5 | from dataclasses import dataclass 6 | from typing import Any 7 | from homeassistant.const import EntityCategory 8 | from homeassistant.components.button import ( 9 | ButtonDeviceClass, 10 | ButtonEntity, 11 | ButtonEntityDescription, 12 | ) 13 | from homeassistant.config_entries import ConfigEntry 14 | from homeassistant.core import HomeAssistant 15 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 16 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 17 | from .const import DOMAIN 18 | from .coordinator import TPLinkRouterCoordinator 19 | 20 | 21 | @dataclass 22 | class TPLinkRouterButtonEntityDescriptionMixin: 23 | method: Callable[[TPLinkRouterCoordinator], Any] 24 | 25 | 26 | @dataclass 27 | class TPLinkButtonEntityDescription( 28 | ButtonEntityDescription, TPLinkRouterButtonEntityDescriptionMixin 29 | ): 30 | """A class that describes button entities for the host.""" 31 | 32 | 33 | BUTTON_TYPES = ( 34 | TPLinkButtonEntityDescription( 35 | key="reboot", 36 | name="Reboot", 37 | device_class=ButtonDeviceClass.RESTART, 38 | entity_category=EntityCategory.CONFIG, 39 | method=lambda coordinator: coordinator.reboot(), 40 | ), 41 | ) 42 | 43 | 44 | async def async_setup_entry( 45 | hass: HomeAssistant, 46 | entry: ConfigEntry, 47 | async_add_entities: AddEntitiesCallback, 48 | ) -> None: 49 | coordinator = hass.data[DOMAIN][entry.entry_id] 50 | 51 | buttons = [] 52 | 53 | for description in BUTTON_TYPES: 54 | buttons.append(TPLinkRouterButtonEntity(coordinator, description)) 55 | async_add_entities(buttons, False) 56 | 57 | 58 | class TPLinkRouterButtonEntity(CoordinatorEntity[TPLinkRouterCoordinator], ButtonEntity): 59 | entity_description: TPLinkButtonEntityDescription 60 | 61 | def __init__( 62 | self, 63 | coordinator: TPLinkRouterCoordinator, 64 | description: TPLinkButtonEntityDescription, 65 | ) -> None: 66 | super().__init__(coordinator) 67 | 68 | self._attr_device_info = coordinator.device_info 69 | self._attr_unique_id = f"{coordinator.unique_id}_{DOMAIN}_{description.key}" 70 | self.entity_description = description 71 | 72 | async def async_press(self) -> None: 73 | await self.entity_description.method(self.coordinator) 74 | -------------------------------------------------------------------------------- /custom_components/tplink_router/config_flow.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import voluptuous as vol 3 | from typing import Any 4 | from homeassistant import config_entries 5 | from homeassistant.core import callback 6 | import homeassistant.helpers.config_validation as cv 7 | from homeassistant.data_entry_flow import FlowResult 8 | from .const import DOMAIN, DEFAULT_USER, DEFAULT_HOST 9 | from .coordinator import TPLinkRouterCoordinator 10 | from homeassistant.const import ( 11 | CONF_HOST, 12 | CONF_PASSWORD, 13 | CONF_USERNAME, 14 | CONF_SCAN_INTERVAL, 15 | CONF_VERIFY_SSL, 16 | ) 17 | 18 | _LOGGER = logging.getLogger(__name__) 19 | 20 | 21 | class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 22 | 23 | async def async_step_user(self, user_input=None): 24 | """Handle the initial step.""" 25 | errors = {} 26 | schema = vol.Schema( 27 | { 28 | vol.Required(CONF_HOST, default=DEFAULT_HOST): str, 29 | vol.Required(CONF_PASSWORD): cv.string, 30 | vol.Required(CONF_SCAN_INTERVAL, default=30): int, 31 | vol.Required(CONF_VERIFY_SSL, default=True): cv.boolean, 32 | } 33 | ) 34 | if user_input is not None: 35 | try: 36 | router = await TPLinkRouterCoordinator.get_client( 37 | hass=self.hass, 38 | host=user_input[CONF_HOST], 39 | password=user_input[CONF_PASSWORD], 40 | username=user_input.get(CONF_USERNAME, DEFAULT_USER), 41 | logger=_LOGGER, 42 | verify_ssl=user_input[CONF_VERIFY_SSL], 43 | ) 44 | await self.hass.async_add_executor_job(router.authorize) 45 | return self.async_create_entry(title=user_input["host"], data=user_input) 46 | except Exception as error: 47 | _LOGGER.error('TplinkRouter Integration Exception - {}'.format(error)) 48 | errors['base'] = str(error) 49 | schema = vol.Schema( 50 | { 51 | vol.Required(CONF_HOST, default=DEFAULT_HOST): str, 52 | vol.Required(CONF_PASSWORD): cv.string, 53 | vol.Required(CONF_USERNAME, default=DEFAULT_USER): str, 54 | vol.Required(CONF_SCAN_INTERVAL, default=30): int, 55 | vol.Required(CONF_VERIFY_SSL, default=True): cv.boolean, 56 | } 57 | ) 58 | 59 | return self.async_show_form(step_id="user", data_schema=schema, errors=errors) 60 | 61 | @staticmethod 62 | @callback 63 | def async_get_options_flow(config_entry: config_entries.ConfigEntry) -> config_entries.OptionsFlow: 64 | return OptionsFlow(config_entry) 65 | 66 | 67 | class OptionsFlow(config_entries.OptionsFlowWithConfigEntry): 68 | 69 | async def async_step_init(self, user_input: dict[str, Any] | None = None) -> FlowResult: 70 | errors = {} 71 | data = user_input or self.config_entry.data 72 | 73 | if user_input is not None: 74 | try: 75 | router = await TPLinkRouterCoordinator.get_client( 76 | hass=self.hass, 77 | host=user_input[CONF_HOST], 78 | password=user_input[CONF_PASSWORD], 79 | username=user_input[CONF_USERNAME], 80 | logger=_LOGGER, 81 | verify_ssl=user_input[CONF_VERIFY_SSL], 82 | ) 83 | await self.hass.async_add_executor_job(router.authorize) 84 | self.hass.config_entries.async_update_entry(self.config_entry, data=user_input) 85 | return self.async_create_entry(title=user_input["host"], data=user_input) 86 | except Exception as error: 87 | _LOGGER.error('TplinkRouter Integration Exception - {}'.format(error)) 88 | errors['base'] = str(error) 89 | 90 | data_schema = vol.Schema({ 91 | vol.Required(CONF_HOST, default=data.get(CONF_HOST)): cv.string, 92 | vol.Required(CONF_USERNAME, default=data.get(CONF_USERNAME, DEFAULT_USER)): cv.string, 93 | vol.Required(CONF_PASSWORD, default=data.get(CONF_PASSWORD)): cv.string, 94 | vol.Required(CONF_SCAN_INTERVAL, default=data.get(CONF_SCAN_INTERVAL)): int, 95 | vol.Required(CONF_VERIFY_SSL, default=data.get(CONF_VERIFY_SSL)): cv.boolean, 96 | }) 97 | 98 | return self.async_show_form(step_id="init", data_schema=data_schema, errors=errors) 99 | -------------------------------------------------------------------------------- /custom_components/tplink_router/coordinator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import hashlib 3 | from datetime import timedelta, datetime 4 | from logging import Logger 5 | from collections.abc import Callable 6 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator 7 | from tplinkrouterc6u import ( 8 | TplinkRouterProvider, 9 | AbstractRouter, 10 | Firmware, 11 | Status, 12 | Connection, 13 | LTEStatus, 14 | SMS, 15 | ) 16 | from homeassistant.core import HomeAssistant 17 | from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo 18 | from .const import ( 19 | DOMAIN, 20 | DEFAULT_NAME, 21 | ) 22 | 23 | 24 | class TPLinkRouterCoordinator(DataUpdateCoordinator): 25 | def __init__( 26 | self, 27 | hass: HomeAssistant, 28 | router: AbstractRouter, 29 | update_interval: int, 30 | firmware: Firmware, 31 | status: Status, 32 | lte_status: LTEStatus | None, 33 | logger: Logger, 34 | unique_id: str 35 | ) -> None: 36 | self.router = router 37 | self.unique_id = unique_id 38 | self.status = status 39 | self.tracked = {} 40 | self.lte_status = lte_status 41 | self.device_info = DeviceInfo( 42 | configuration_url=router.host, 43 | connections={(CONNECTION_NETWORK_MAC, self.status.lan_macaddr)}, 44 | identifiers={(DOMAIN, self.status.lan_macaddr)}, 45 | manufacturer="TPLink", 46 | model=firmware.model, 47 | name=DEFAULT_NAME, 48 | sw_version=firmware.firmware_version, 49 | hw_version=firmware.hardware_version, 50 | ) 51 | 52 | self.scan_stopped_at: datetime | None = None 53 | self._last_update_time: datetime | None = None 54 | self._sms_hashes: set[str] = set() 55 | self.new_sms: list[SMS] = [] 56 | 57 | super().__init__( 58 | hass, 59 | logger, 60 | name=DOMAIN, 61 | update_interval=timedelta(seconds=update_interval), 62 | ) 63 | 64 | @staticmethod 65 | async def get_client(hass: HomeAssistant, host: str, password: str, username: str, logger: Logger, 66 | verify_ssl: bool) -> AbstractRouter: 67 | return await hass.async_add_executor_job(TplinkRouterProvider.get_client, host, password, username, 68 | logger, verify_ssl) 69 | 70 | @staticmethod 71 | def request(router: AbstractRouter, callback: Callable): 72 | router.authorize() 73 | data = callback() 74 | router.logout() 75 | 76 | return data 77 | 78 | async def reboot(self) -> None: 79 | await self.hass.async_add_executor_job(TPLinkRouterCoordinator.request, self.router, self.router.reboot) 80 | 81 | async def set_wifi(self, wifi: Connection, enable: bool) -> None: 82 | def callback(): 83 | self.router.set_wifi(wifi, enable) 84 | 85 | await self.hass.async_add_executor_job(TPLinkRouterCoordinator.request, self.router, callback) 86 | 87 | async def _async_update_data(self): 88 | """Asynchronous update of all data.""" 89 | if self.scan_stopped_at is not None and self.scan_stopped_at > (datetime.now() - timedelta(minutes=20)): 90 | return 91 | self.scan_stopped_at = None 92 | self.status = await self.hass.async_add_executor_job(TPLinkRouterCoordinator.request, self.router, 93 | self.router.get_status) 94 | # Only fetch if router is lte_status compatible 95 | if self.lte_status is not None: 96 | self.lte_status = await self.hass.async_add_executor_job( 97 | TPLinkRouterCoordinator.request, 98 | self.router, 99 | self.router.get_lte_status, 100 | ) 101 | await self._update_new_sms() 102 | self._last_update_time = datetime.now() 103 | 104 | async def _update_new_sms(self) -> None: 105 | if not hasattr(self.router, "get_sms") or self.lte_status is None: 106 | return 107 | sms_list = await self.hass.async_add_executor_job(TPLinkRouterCoordinator.request, self.router, 108 | self.router.get_sms) 109 | new_items = [] 110 | for sms in sms_list: 111 | h = TPLinkRouterCoordinator._hash_item(sms) 112 | if self._last_update_time is None: 113 | self._sms_hashes.add(h) 114 | elif h not in self._sms_hashes: 115 | self._sms_hashes.add(h) 116 | new_items.append(sms) 117 | 118 | self.new_sms = new_items 119 | 120 | @staticmethod 121 | def _hash_item(sms: SMS) -> str: 122 | key = f"{sms.sender}|{sms.content}|{sms.received_at.isoformat()}" 123 | return hashlib.sha1(key.encode("utf-8")).hexdigest() 124 | -------------------------------------------------------------------------------- /custom_components/tplink_router/__init__.py: -------------------------------------------------------------------------------- 1 | from homeassistant.const import ( 2 | CONF_HOST, 3 | CONF_PASSWORD, 4 | CONF_USERNAME, 5 | CONF_SCAN_INTERVAL, 6 | CONF_VERIFY_SSL, 7 | Platform, 8 | ) 9 | from homeassistant.core import HomeAssistant, ServiceCall 10 | from homeassistant.config_entries import ConfigEntry 11 | from .const import DOMAIN, DEFAULT_USER, EVENT_NEW_SMS 12 | import logging 13 | from .coordinator import TPLinkRouterCoordinator 14 | from homeassistant.helpers import device_registry 15 | 16 | PLATFORMS: list[Platform] = [ 17 | Platform.DEVICE_TRACKER, 18 | Platform.SENSOR, 19 | Platform.SWITCH, 20 | Platform.BUTTON, 21 | ] 22 | 23 | _LOGGER = logging.getLogger(__name__) 24 | 25 | 26 | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 27 | # Construct the device 28 | host = entry.data[CONF_HOST] 29 | if not (host.startswith('http://') or host.startswith('https://')): 30 | host = "http://{}".format(host) 31 | verify_ssl = entry.data[CONF_VERIFY_SSL] if CONF_VERIFY_SSL in entry.data else True 32 | client = await TPLinkRouterCoordinator.get_client( 33 | hass=hass, 34 | host=host, 35 | password=entry.data[CONF_PASSWORD], 36 | username=entry.data.get(CONF_USERNAME, DEFAULT_USER), 37 | logger=_LOGGER, 38 | verify_ssl=verify_ssl 39 | ) 40 | 41 | def callback(): 42 | firm = client.get_firmware() 43 | stat = client.get_status() 44 | # Check if router is lte_status compatible 45 | lte_stat = None 46 | if hasattr(client, "get_lte_status"): 47 | try: 48 | lte_stat = client.get_lte_status() 49 | except Exception: 50 | pass 51 | 52 | return firm, stat, lte_stat 53 | 54 | firmware, status, lte_status = await hass.async_add_executor_job(TPLinkRouterCoordinator.request, client, callback) 55 | 56 | # Create device coordinator and fetch data 57 | coordinator = TPLinkRouterCoordinator(hass, client, entry.data[CONF_SCAN_INTERVAL], firmware, status, 58 | lte_status, _LOGGER, entry.entry_id) 59 | 60 | await coordinator.async_config_entry_first_refresh() 61 | _async_add_listeners(hass, coordinator) 62 | hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator 63 | 64 | await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) 65 | entry.async_on_unload(entry.add_update_listener(async_reload_entry)) 66 | 67 | register_services(hass, coordinator) 68 | 69 | return True 70 | 71 | 72 | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 73 | unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) 74 | 75 | if unload_ok: 76 | hass.data[DOMAIN].pop(entry.entry_id) 77 | return unload_ok 78 | 79 | 80 | async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None: 81 | await hass.config_entries.async_reload(config_entry.entry_id) 82 | 83 | 84 | def register_services(hass: HomeAssistant, coord: TPLinkRouterCoordinator) -> None: 85 | 86 | if not hasattr(coord.router, "send_sms") or coord.lte_status is None: 87 | return 88 | 89 | dr = device_registry.async_get(hass) 90 | 91 | async def send_sms_service(service: ServiceCall) -> None: 92 | device = dr.async_get(service.data.get("device")) 93 | if device is None: 94 | _LOGGER.error('TplinkRouter Integration Exception - device was not found') 95 | return 96 | coordinator = None 97 | for key in device.config_entries: 98 | entry = hass.config_entries.async_get_entry(key) 99 | if not entry: 100 | continue 101 | if entry.domain != DOMAIN or not hasattr(hass.data[DOMAIN][key].router, "send_sms"): 102 | continue 103 | coordinator = hass.data[DOMAIN][key] 104 | 105 | if coordinator is None: 106 | _LOGGER.error('TplinkRouter Integration Exception - This device cannot send SMS') 107 | return 108 | 109 | def callback(): 110 | coord.router.send_sms(service.data.get("number"), service.data.get("text")) 111 | await hass.async_add_executor_job(TPLinkRouterCoordinator.request, coord.router, callback) 112 | 113 | if not hass.services.has_service(DOMAIN, 'send_sms'): 114 | hass.services.async_register(DOMAIN, 'send_sms', send_sms_service) 115 | 116 | 117 | def _async_add_listeners(hass: HomeAssistant, coord: TPLinkRouterCoordinator) -> None: 118 | 119 | if not hasattr(coord.router, "get_sms") or coord.lte_status is None: 120 | return 121 | 122 | coord.async_add_listener( 123 | lambda: _fire_sms_event(hass, coord) 124 | ) 125 | 126 | 127 | def _fire_sms_event(hass: HomeAssistant, coord: TPLinkRouterCoordinator) -> None: 128 | for sms in coord.new_sms: 129 | hass.bus.fire( 130 | EVENT_NEW_SMS, 131 | { 132 | 'sender': sms.sender, 133 | 'content': sms.content, 134 | 'received_at': sms.received_at.isoformat(), 135 | }, 136 | ) 137 | coord.new_sms = [] 138 | -------------------------------------------------------------------------------- /custom_components/tplink_router/device_tracker.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TypeAlias 4 | from homeassistant.components.device_tracker.config_entry import ScannerEntity 5 | from homeassistant.components.device_tracker.const import SourceType 6 | from homeassistant.config_entries import ConfigEntry 7 | from homeassistant.core import HomeAssistant, callback 8 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 9 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 10 | from .coordinator import TPLinkRouterCoordinator 11 | from .const import ( 12 | DOMAIN, 13 | EVENT_NEW_DEVICE, 14 | EVENT_ONLINE, 15 | EVENT_OFFLINE, 16 | ) 17 | from tplinkrouterc6u import Device 18 | 19 | MAC_ADDR: TypeAlias = str 20 | 21 | 22 | async def async_setup_entry( 23 | hass: HomeAssistant, 24 | entry: ConfigEntry, 25 | async_add_entities: AddEntitiesCallback, 26 | ) -> None: 27 | coordinator = hass.data[DOMAIN][entry.entry_id] 28 | tracked: dict[MAC_ADDR, TPLinkTracker] = {} 29 | 30 | @callback 31 | def coordinator_updated(): 32 | """Update the status of the device.""" 33 | update_items(coordinator, async_add_entities, tracked) 34 | 35 | entry.async_on_unload(coordinator.async_add_listener(coordinator_updated)) 36 | coordinator_updated() 37 | 38 | 39 | @callback 40 | def update_items( 41 | coordinator: TPLinkRouterCoordinator, 42 | async_add_entities: AddEntitiesCallback, 43 | tracked: dict[MAC_ADDR, TPLinkTracker], 44 | ) -> None: 45 | """Update tracked device state from the hub.""" 46 | new_tracked: list[TPLinkTracker] = [] 47 | active: list[MAC_ADDR] = [] 48 | fire_event = tracked != {} 49 | for device in coordinator.status.devices: 50 | active.append(device.macaddr) 51 | if device.macaddr not in tracked: 52 | tracked[device.macaddr] = TPLinkTracker(coordinator, device) 53 | new_tracked.append(tracked[device.macaddr]) 54 | if fire_event: 55 | coordinator.hass.bus.fire(EVENT_NEW_DEVICE, tracked[device.macaddr].data) 56 | else: 57 | tracked[device.macaddr].device = device 58 | if fire_event and not tracked[device.macaddr].active and device.active: 59 | coordinator.hass.bus.fire(EVENT_ONLINE, tracked[device.macaddr].data) 60 | if fire_event and tracked[device.macaddr].active and not device.active: 61 | coordinator.hass.bus.fire(EVENT_OFFLINE, tracked[device.macaddr].data) 62 | tracked[device.macaddr].active = device.active 63 | 64 | if new_tracked: 65 | async_add_entities(new_tracked) 66 | 67 | for mac in tracked: 68 | if mac not in active and tracked[mac].active: 69 | tracked[mac].active = False 70 | coordinator.hass.bus.fire(EVENT_OFFLINE, tracked[mac].data) 71 | 72 | 73 | class TPLinkTracker(CoordinatorEntity, ScannerEntity): 74 | """Representation of network device.""" 75 | 76 | def __init__( 77 | self, 78 | coordinator: TPLinkRouterCoordinator, 79 | data: Device, 80 | ) -> None: 81 | """Initialize the tracked device.""" 82 | self.device = data 83 | self.active = True 84 | 85 | super().__init__(coordinator) 86 | 87 | @property 88 | def is_connected(self) -> bool: 89 | """Return true if the client is connected to the network.""" 90 | return self.active 91 | 92 | @property 93 | def source_type(self) -> str: 94 | """Return the source type of the client.""" 95 | return SourceType.ROUTER 96 | 97 | @property 98 | def name(self) -> str: 99 | """Return the name of the client.""" 100 | return self.device.hostname if self.device.hostname != '' else self.device.macaddr 101 | 102 | @property 103 | def hostname(self) -> str: 104 | """Return the hostname of the client.""" 105 | return self.device.hostname 106 | 107 | @property 108 | def mac_address(self) -> MAC_ADDR: 109 | """Return the mac address of the client.""" 110 | return self.device.macaddr 111 | 112 | @property 113 | def ip_address(self) -> str: 114 | """Return the ip address of the client.""" 115 | return self.device.ipaddr 116 | 117 | @property 118 | def unique_id(self) -> str: 119 | """Return an unique identifier for this device.""" 120 | return f"{self.coordinator.unique_id}_{DOMAIN}_{self.mac_address}" 121 | 122 | @property 123 | def icon(self) -> str: 124 | """Return device icon.""" 125 | return "mdi:lan-connect" if self.is_connected else "mdi:lan-disconnect" 126 | 127 | @property 128 | def extra_state_attributes(self) -> dict[str, str]: 129 | attributes = { 130 | 'connection': self.device.type.get_type(), 131 | 'band': self.device.type.get_band(), 132 | 'packets_sent': self.device.packets_sent, 133 | 'packets_received': self.device.packets_received 134 | } 135 | if self.device.down_speed is not None or self.device.up_speed is not None: 136 | attributes['up_speed'] = self.device.up_speed 137 | attributes['down_speed'] = self.device.down_speed 138 | return attributes 139 | 140 | @property 141 | def data(self) -> dict[str, str]: 142 | return dict(self.extra_state_attributes.items() | { 143 | 'hostname': self.hostname, 144 | 'ip_address': self.ip_address, 145 | 'mac_address': self.mac_address, 146 | }.items()) 147 | 148 | @property 149 | def entity_registry_enabled_default(self) -> bool: 150 | return True 151 | -------------------------------------------------------------------------------- /custom_components/tplink_router/switch.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from collections.abc import Callable 3 | from dataclasses import dataclass 4 | from datetime import datetime 5 | from typing import Any 6 | from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription 7 | from homeassistant.config_entries import ConfigEntry 8 | from homeassistant.const import EntityCategory 9 | from homeassistant.core import HomeAssistant 10 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 11 | from .const import DOMAIN 12 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 13 | from .coordinator import TPLinkRouterCoordinator 14 | from tplinkrouterc6u import Connection 15 | 16 | 17 | @dataclass 18 | class TPLinkRouterSwitchEntityDescriptionMixin: 19 | method: Callable[[TPLinkRouterCoordinator, bool], Any] 20 | property: str 21 | 22 | 23 | @dataclass 24 | class TPLinkRouterSwitchEntityDescription(SwitchEntityDescription, TPLinkRouterSwitchEntityDescriptionMixin): 25 | """A class that describes sensor entities.""" 26 | 27 | 28 | SWITCH_TYPES = ( 29 | TPLinkRouterSwitchEntityDescription( 30 | key="wifi_guest_24g", 31 | name="Guest WIFI 2.4G", 32 | icon="mdi:wifi", 33 | entity_category=EntityCategory.CONFIG, 34 | property='guest_2g_enable', 35 | method=lambda coordinator, value: coordinator.set_wifi(Connection.GUEST_2G, value), 36 | ), 37 | TPLinkRouterSwitchEntityDescription( 38 | key="wifi_guest_5g", 39 | name="Guest WIFI 5G", 40 | icon="mdi:wifi", 41 | entity_category=EntityCategory.CONFIG, 42 | property='guest_5g_enable', 43 | method=lambda coordinator, value: coordinator.set_wifi(Connection.GUEST_5G, value), 44 | ), 45 | TPLinkRouterSwitchEntityDescription( 46 | key="wifi_guest_6g", 47 | name="Guest WIFI 6G", 48 | icon="mdi:wifi", 49 | entity_category=EntityCategory.CONFIG, 50 | property='guest_6g_enable', 51 | method=lambda coordinator, value: coordinator.set_wifi(Connection.GUEST_6G, value), 52 | ), 53 | TPLinkRouterSwitchEntityDescription( 54 | key="wifi_24g", 55 | name="WIFI 2.4G", 56 | icon="mdi:wifi", 57 | entity_category=EntityCategory.CONFIG, 58 | property='wifi_2g_enable', 59 | method=lambda coordinator, value: coordinator.set_wifi(Connection.HOST_2G, value), 60 | ), 61 | TPLinkRouterSwitchEntityDescription( 62 | key="wifi_5g", 63 | name="WIFI 5G", 64 | icon="mdi:wifi", 65 | entity_category=EntityCategory.CONFIG, 66 | property='wifi_5g_enable', 67 | method=lambda coordinator, value: coordinator.set_wifi(Connection.HOST_5G, value), 68 | ), 69 | TPLinkRouterSwitchEntityDescription( 70 | key="wifi_6g", 71 | name="WIFI 6G", 72 | icon="mdi:wifi", 73 | entity_category=EntityCategory.CONFIG, 74 | property='wifi_6g_enable', 75 | method=lambda coordinator, value: coordinator.set_wifi(Connection.HOST_6G, value), 76 | ), 77 | TPLinkRouterSwitchEntityDescription( 78 | key="iot_24g", 79 | name="IoT WIFI 2.4G", 80 | icon="mdi:wifi", 81 | entity_category=EntityCategory.CONFIG, 82 | property='iot_2g_enable', 83 | method=lambda coordinator, value: coordinator.set_wifi(Connection.IOT_2G, value), 84 | ), 85 | TPLinkRouterSwitchEntityDescription( 86 | key="iot_5g", 87 | name="IoT WIFI 5G", 88 | icon="mdi:wifi", 89 | entity_category=EntityCategory.CONFIG, 90 | property='iot_5g_enable', 91 | method=lambda coordinator, value: coordinator.set_wifi(Connection.IOT_5G, value), 92 | ), 93 | TPLinkRouterSwitchEntityDescription( 94 | key="iot_6g", 95 | name="IoT WIFI 6G", 96 | icon="mdi:wifi", 97 | entity_category=EntityCategory.CONFIG, 98 | property='iot_6g_enable', 99 | method=lambda coordinator, value: coordinator.set_wifi(Connection.IOT_6G, value), 100 | ), 101 | ) 102 | 103 | 104 | async def async_setup_entry( 105 | hass: HomeAssistant, 106 | entry: ConfigEntry, 107 | async_add_entities: AddEntitiesCallback, 108 | ) -> None: 109 | coordinator = hass.data[DOMAIN][entry.entry_id] 110 | 111 | switches = [] 112 | 113 | for description in SWITCH_TYPES: 114 | switches.append(TPLinkRouterSwitchEntity(coordinator, description)) 115 | 116 | switches.append(TPLinkRouterScanEntity(coordinator)) 117 | 118 | async_add_entities(switches, False) 119 | 120 | 121 | class TPLinkRouterSwitchEntity( 122 | CoordinatorEntity[TPLinkRouterCoordinator], SwitchEntity 123 | ): 124 | entity_description: TPLinkRouterSwitchEntityDescription 125 | 126 | def __init__( 127 | self, 128 | coordinator: TPLinkRouterCoordinator, 129 | description: TPLinkRouterSwitchEntityDescription, 130 | ) -> None: 131 | super().__init__(coordinator) 132 | 133 | self._attr_device_info = coordinator.device_info 134 | self._attr_unique_id = f"{coordinator.unique_id}_{DOMAIN}_{description.key}" 135 | self.entity_description = description 136 | 137 | @property 138 | def is_on(self) -> bool: 139 | """Return true if switch is on.""" 140 | return getattr(self.coordinator.status, self.entity_description.property) 141 | 142 | @property 143 | def available(self) -> bool: 144 | """Return True if entity is available.""" 145 | return getattr(self.coordinator.status, self.entity_description.property) is not None 146 | 147 | async def async_turn_on(self, **kwargs: Any) -> None: 148 | """Turn the entity on.""" 149 | await self.entity_description.method(self.coordinator, True) 150 | setattr(self.coordinator.status, self.entity_description.property, True) 151 | self.async_write_ha_state() 152 | 153 | async def async_turn_off(self, **kwargs: Any) -> None: 154 | """Turn the entity off.""" 155 | await self.entity_description.method(self.coordinator, False) 156 | setattr(self.coordinator.status, self.entity_description.property, False) 157 | self.async_write_ha_state() 158 | 159 | 160 | class TPLinkRouterScanEntity( 161 | CoordinatorEntity[TPLinkRouterCoordinator], SwitchEntity 162 | ): 163 | entity_description: SwitchEntityDescription 164 | 165 | def __init__(self, coordinator: TPLinkRouterCoordinator) -> None: 166 | super().__init__(coordinator) 167 | 168 | self._attr_device_info = coordinator.device_info 169 | self.entity_description = SwitchEntityDescription( 170 | key="scanning", 171 | name="Router data fetching", 172 | icon="mdi:connection", 173 | entity_category=EntityCategory.CONFIG, 174 | ) 175 | self._attr_unique_id = f"{coordinator.unique_id}_{DOMAIN}_{self.entity_description.key}" 176 | 177 | @property 178 | def is_on(self) -> bool: 179 | """Return true if switch is on.""" 180 | return self.coordinator.scan_stopped_at is None 181 | 182 | async def async_turn_on(self, **kwargs: Any) -> None: 183 | """Turn the entity on.""" 184 | self.coordinator.scan_stopped_at = None 185 | self.async_write_ha_state() 186 | 187 | async def async_turn_off(self, **kwargs: Any) -> None: 188 | """Turn the entity off.""" 189 | self.coordinator.scan_stopped_at = datetime.now() 190 | self.async_write_ha_state() 191 | -------------------------------------------------------------------------------- /custom_components/tplink_router/sensor.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from collections.abc import Callable 3 | from typing import Any 4 | from homeassistant.components.sensor import ( 5 | SensorStateClass, 6 | SensorEntity, 7 | SensorEntityDescription, 8 | ) 9 | from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, UnitOfDataRate, UnitOfInformation 10 | from homeassistant.config_entries import ConfigEntry 11 | from homeassistant.core import HomeAssistant, callback 12 | from .const import DOMAIN 13 | from homeassistant.helpers.entity_platform import AddEntitiesCallback 14 | from homeassistant.helpers.update_coordinator import CoordinatorEntity 15 | from .coordinator import TPLinkRouterCoordinator 16 | from tplinkrouterc6u import Status, LTEStatus 17 | 18 | 19 | @dataclass 20 | class TPLinkRouterSensorRequiredKeysMixin: 21 | value: Callable[[Status], Any] 22 | 23 | 24 | @dataclass 25 | class TPLinkRouterLTESensorRequiredKeysMixin: 26 | value: Callable[[LTEStatus], Any] 27 | 28 | 29 | @dataclass 30 | class TPLinkRouterSensorEntityDescription( 31 | SensorEntityDescription, TPLinkRouterSensorRequiredKeysMixin 32 | ): 33 | """A class that describes sensor entities.""" 34 | 35 | sensor_type: str = "status" 36 | 37 | 38 | @dataclass 39 | class TPLinkRouterLTESensorEntityDescription( 40 | SensorEntityDescription, TPLinkRouterLTESensorRequiredKeysMixin 41 | ): 42 | """A class that describes LTEStatus entities.""" 43 | 44 | sensor_type: str = "lte_status" 45 | 46 | 47 | SENSOR_TYPES: tuple[TPLinkRouterSensorEntityDescription, ...] = ( 48 | TPLinkRouterSensorEntityDescription( 49 | key="guest_wifi_clients_total", 50 | name="Total guest wifi clients", 51 | icon="mdi:account-multiple", 52 | state_class=SensorStateClass.TOTAL, 53 | value=lambda status: status.guest_clients_total, 54 | ), 55 | TPLinkRouterSensorEntityDescription( 56 | key="wifi_clients_total", 57 | name="Total main wifi clients", 58 | icon="mdi:account-multiple", 59 | state_class=SensorStateClass.TOTAL, 60 | value=lambda status: status.wifi_clients_total, 61 | ), 62 | TPLinkRouterSensorEntityDescription( 63 | key="wired_clients_total", 64 | name="Total wired clients", 65 | icon="mdi:account-multiple", 66 | state_class=SensorStateClass.TOTAL, 67 | value=lambda status: status.wired_total, 68 | ), 69 | TPLinkRouterSensorEntityDescription( 70 | key="iot_clients_total", 71 | name="Total IoT clients", 72 | icon="mdi:account-multiple", 73 | state_class=SensorStateClass.TOTAL, 74 | value=lambda status: status.iot_clients_total, 75 | ), 76 | TPLinkRouterSensorEntityDescription( 77 | key="clients_total", 78 | name="Total clients", 79 | icon="mdi:account-multiple", 80 | state_class=SensorStateClass.TOTAL, 81 | value=lambda status: status.clients_total, 82 | ), 83 | TPLinkRouterSensorEntityDescription( 84 | key="cpu_used", 85 | name="CPU used", 86 | icon="mdi:cpu-64-bit", 87 | state_class=SensorStateClass.MEASUREMENT, 88 | native_unit_of_measurement=PERCENTAGE, 89 | suggested_display_precision=1, 90 | value=lambda status: (status.cpu_usage * 100) if status.cpu_usage is not None else None, 91 | ), 92 | TPLinkRouterSensorEntityDescription( 93 | key="memory_used", 94 | name="Memory used", 95 | icon="mdi:memory", 96 | state_class=SensorStateClass.MEASUREMENT, 97 | native_unit_of_measurement=PERCENTAGE, 98 | suggested_display_precision=1, 99 | value=lambda status: (status.mem_usage * 100) if status.mem_usage is not None else None, 100 | ), 101 | TPLinkRouterSensorEntityDescription( 102 | key="conn_type", 103 | name="Connection Type", 104 | icon="mdi:wan", 105 | value=lambda status: status.conn_type, 106 | ), 107 | ) 108 | 109 | LTE_SENSOR_TYPES: tuple[TPLinkRouterLTESensorEntityDescription, ...] = ( 110 | TPLinkRouterLTESensorEntityDescription( 111 | key="lte_enabled", 112 | name="LTE Enabled", 113 | icon="mdi:sim-outline", 114 | value=lambda status: status.enable, 115 | ), 116 | TPLinkRouterLTESensorEntityDescription( 117 | key="lte_connect_status", 118 | name="LTE Connection Status", 119 | icon="mdi:sim-outline", 120 | value=lambda status: status.connect_status, 121 | ), 122 | TPLinkRouterLTESensorEntityDescription( 123 | key="lte_network_type", 124 | name="LTE Network Type", 125 | icon="mdi:sim-outline", 126 | value=lambda status: status.network_type, 127 | ), 128 | TPLinkRouterLTESensorEntityDescription( 129 | key="lte_network_type_info", 130 | name="LTE Network Type Info", 131 | icon="mdi:sim-outline", 132 | value=lambda status: status.network_type_info, 133 | ), 134 | TPLinkRouterLTESensorEntityDescription( 135 | key="lte_sim_status", 136 | name="LTE SIM Status", 137 | icon="mdi:sim-outline", 138 | value=lambda status: status.sim_status, 139 | ), 140 | TPLinkRouterLTESensorEntityDescription( 141 | key="lte_sim_status_info", 142 | name="LTE SIM Status Info", 143 | icon="mdi:sim-outline", 144 | value=lambda status: status.sim_status_info, 145 | ), 146 | TPLinkRouterLTESensorEntityDescription( 147 | key="lte_total_statistics", 148 | name="LTE Total Statistics", 149 | icon="mdi:sim-outline", 150 | state_class=SensorStateClass.TOTAL, 151 | native_unit_of_measurement=UnitOfInformation.BYTES, 152 | value=lambda status: status.total_statistics, 153 | ), 154 | TPLinkRouterLTESensorEntityDescription( 155 | key="lte_cur_rx_speed", 156 | name="LTE Current RX Speed", 157 | icon="mdi:sim-outline", 158 | state_class=SensorStateClass.MEASUREMENT, 159 | native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND, 160 | value=lambda status: status.cur_rx_speed, 161 | ), 162 | TPLinkRouterLTESensorEntityDescription( 163 | key="lte_cur_tx_speed", 164 | name="LTE Current TX Speed", 165 | icon="mdi:sim-outline", 166 | state_class=SensorStateClass.MEASUREMENT, 167 | native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND, 168 | value=lambda status: status.cur_tx_speed, 169 | ), 170 | TPLinkRouterLTESensorEntityDescription( 171 | key="lte_sms_unread_count", 172 | name="Unread SMS", 173 | icon="mdi:sim-outline", 174 | state_class=SensorStateClass.TOTAL, 175 | value=lambda status: status.sms_unread_count, 176 | ), 177 | TPLinkRouterLTESensorEntityDescription( 178 | key="lte_sig_level", 179 | name="LTE Signal Level", 180 | icon="mdi:sim-outline", 181 | state_class=SensorStateClass.MEASUREMENT, 182 | native_unit_of_measurement=PERCENTAGE, 183 | value=lambda status: status.sig_level * 25, 184 | ), 185 | TPLinkRouterLTESensorEntityDescription( 186 | key="lte_rsrp", 187 | name="LTE RSRP", 188 | icon="mdi:sim-outline", 189 | state_class=SensorStateClass.MEASUREMENT, 190 | native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, 191 | value=lambda status: status.rsrp, 192 | ), 193 | TPLinkRouterLTESensorEntityDescription( 194 | key="lte_rsrq", 195 | name="LTE RSRQ", 196 | icon="mdi:sim-outline", 197 | state_class=SensorStateClass.MEASUREMENT, 198 | native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, 199 | value=lambda status: status.rsrq, 200 | ), 201 | TPLinkRouterLTESensorEntityDescription( 202 | key="lte_snr", 203 | name="LTE SNR", 204 | icon="mdi:sim-outline", 205 | state_class=SensorStateClass.MEASUREMENT, 206 | native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, 207 | value=lambda status: 0.1 * status.snr, 208 | ), 209 | TPLinkRouterLTESensorEntityDescription( 210 | key="lte_isp_name", 211 | name="LTE ISP Name", 212 | icon="mdi:sim-outline", 213 | value=lambda status: status.isp_name, 214 | ), 215 | ) 216 | 217 | 218 | async def async_setup_entry( 219 | hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback 220 | ) -> None: 221 | coordinator = hass.data[DOMAIN][entry.entry_id] 222 | 223 | sensors = [] 224 | 225 | for description in SENSOR_TYPES: 226 | sensors.append(TPLinkRouterSensor(coordinator, description)) 227 | 228 | if coordinator.lte_status is not None: 229 | for description in LTE_SENSOR_TYPES: 230 | sensors.append(TPLinkRouterSensor(coordinator, description)) 231 | 232 | async_add_entities(sensors, False) 233 | 234 | 235 | class TPLinkRouterSensor( 236 | CoordinatorEntity[TPLinkRouterCoordinator], SensorEntity 237 | ): 238 | _attr_has_entity_name = True 239 | entity_description: TPLinkRouterSensorEntityDescription | TPLinkRouterLTESensorEntityDescription 240 | 241 | def __init__( 242 | self, 243 | coordinator: TPLinkRouterCoordinator, 244 | description: TPLinkRouterSensorEntityDescription | TPLinkRouterLTESensorEntityDescription, 245 | ) -> None: 246 | super().__init__(coordinator) 247 | 248 | self._attr_device_info = coordinator.device_info 249 | self._attr_unique_id = f"{coordinator.unique_id}_{DOMAIN}_{description.key}" 250 | self.entity_description = description 251 | 252 | @callback 253 | def _handle_coordinator_update(self) -> None: 254 | """Handle updated data from the coordinator.""" 255 | coordinator_data = getattr(self.coordinator, self.entity_description.sensor_type) 256 | self._attr_native_value = self.entity_description.value(coordinator_data) 257 | self.async_write_ha_state() 258 | 259 | @property 260 | def available(self) -> bool: 261 | """Return True if entity is available.""" 262 | coordinator_data = getattr(self.coordinator, self.entity_description.sensor_type) 263 | return self.entity_description.value(coordinator_data) is not None 264 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tp-Link router integration for Home Assistant (supports also Mercusys router) 2 | [![version](https://img.shields.io/github/manifest-json/v/AlexandrErohin/home-assistant-tplink-router?filename=custom_components%2Ftplink_router%2Fmanifest.json&color=slateblue)](https://github.com/AlexandrErohin/home-assistant-tplink-router/releases/latest) 3 | [![HACS](https://img.shields.io/badge/HACS-Default-orange.svg?logo=HomeAssistantCommunityStore&logoColor=white)](https://github.com/hacs/integration) 4 | [![Community Forum](https://img.shields.io/static/v1.svg?label=Community&message=Forum&color=41bdf5&logo=HomeAssistant&logoColor=white)](https://community.home-assistant.io/t/custom-component-tp-link-router-integration) 5 | 6 | Home Assistant component for TP-Link and Mercusys routers administration based on the [TP-Link Router API](https://github.com/AlexandrErohin/TP-Link-Archer-C6U) 7 | 8 | > [!WARNING] 9 | > A new router firmware update breaks the compatibility. Please try [this fix](https://github.com/AlexandrErohin/home-assistant-tplink-router/issues/220#issuecomment-3396658175) 10 | 11 | > [!WARNING] 12 | > Please temporarily disable the integration before accessing the router admin page. TP-Link admin page only allows one user at a time. This integration will log you out of the admin page every time it scans for updates (every 30s by default). 13 | 14 | See [Supported routers](#supports) 15 | 16 | 17 | 18 | ## Components 19 | ### Events 20 | - tplink_router_new_device: Fired when a new device appears in your network 21 | - tplink_router_device_offline: Fired when a device becomes offline 22 | - tplink_router_device_online: Fired when a device becomes online 23 | - tplink_router_new_sms: Fired when a new sms received by LTE router 24 | ### Switches 25 | - Router Reboot 26 | - Router data fetching - you may disable router data fetching before accessing the router, so it wont logging your out. 27 | If you forget to enable it back - it would be automatically enable after 20 minutes 28 | - 2.4Ghz host wifi Enable/Disable 29 | - 5Ghz host wifi Enable/Disable 30 | - 6Ghz host wifi Enable/Disable 31 | - 2.4Ghz guest wifi Enable/Disable 32 | - 5Ghz guest wifi Enable/Disable 33 | - 6Ghz guest wifi Enable/Disable 34 | - 2.4Ghz IoT wifi network Enable/Disable 35 | - 5Ghz IoT wifi network Enable/Disable 36 | - 6Ghz IoT wifi network Enable/Disable 37 | 38 | ### Sensors 39 | - Total amount of wired clients 40 | - Total amount of IoT clients 41 | - Total amount of host wifi clients 42 | - Total amount of guest wifi clients 43 | - Total amount of all connected clients 44 | - CPU used 45 | - Memory used 46 | - Connection Type 47 | 48 | For LTE Routers 49 | - LTE Enabled 50 | - LTE Connection Status 51 | - LTE Network Type 52 | - LTE SIM Status 53 | - LTE Total Statistics 54 | - LTE Current RX Speed 55 | - LTE Current TX Speed 56 | - Unread SMS 57 | - LTE Signal Level 58 | - LTE RSRP 59 | - LTE RSRQ 60 | - LTE SNR 61 | - LTE ISP Name 62 | 63 | ### Device Tracker 64 | - Track connected to router devices by MAC address with connection information 65 | 66 | To find your device - Go to `Developer tools` and search for your MAC address - you’ll find sensor like `device_tracker.YOUR_MAC` or `device_tracker.YOUR_PHONE_NAME`. 67 | 68 | It will also fire Home Assistant event when a device connects to router 69 | 70 | ### Services 71 | - Send SMS message - Available only for MR LTE routers 72 | 73 | ### Notification 74 | #### Device events 75 | To receive notifications of appearing a new device in your network, or becoming device online\offline add following lines to your `configuration.yaml` file: 76 | ```yaml 77 | automation: 78 | - alias: "New network device" 79 | trigger: 80 | platform: event 81 | event_type: tplink_router_new_device 82 | action: 83 | service: notify.mobile_app_ 84 | data: 85 | content: >- 86 | New device appear {{ trigger.event.data.hostname }} with IP {{ trigger.event.data.ip_address }} 87 | ``` 88 | Available events: 89 | - tplink_router_new_device: Fired when a new device appears in your network 90 | - tplink_router_device_offline: Fired when a device becomes offline 91 | - tplink_router_device_online: Fired when a device becomes online 92 | 93 | All available fields in `trigger.event.data`: 94 | - hostname 95 | - ip_address 96 | - mac_address 97 | - connection 98 | - band 99 | - packets_sent 100 | - packets_received 101 | 102 | #### SMS events only for MR LTE routers 103 | To receive notifications of receiving a new sms add following lines to your `configuration.yaml` file: 104 | ```yaml 105 | automation: 106 | - alias: "New sms" 107 | trigger: 108 | platform: event 109 | event_type: tplink_router_new_sms 110 | action: 111 | service: notify.mobile_app_ 112 | data: 113 | content: >- 114 | A new SMS from {{ trigger.event.data.sender }} wth text: {{ trigger.event.data.content }} 115 | ``` 116 | Available events: 117 | - tplink_router_new_sms: Fired when a new sms received by LTE router 118 | 119 | All available fields in `trigger.event.data`: 120 | - sender 121 | - content 122 | - received_at 123 | 124 | ### Send SMS only for MR LTE routers 125 | To send SMS add following lines to your automation in yaml: 126 | ```yaml 127 | ... 128 | action: 129 | - service: tplink_router.send_sms 130 | data: 131 | number: "+1234567890" 132 | text: "Hello World" 133 | device: pass_tplink_router_device_id_here 134 | ``` 135 | 136 | Device id is required because user may have several routers that could send SMS - so you need to select the needed router. 137 | You can get the ID from the URL when you visit the tplink device page 138 | 139 | ## Installation 140 | 141 | ### HACS (recommended) 142 | 143 | Have [HACS](https://hacs.xyz/) installed, this will allow you to update easily. 144 | 145 | Open your Home Assistant instance and open a repository inside the Home Assistant Community Store. 146 | 147 | or go to Hacs and search for `TP-Link Router`. 148 | 149 | ### Manual 150 | 151 | 1. Locate the `custom_components` directory in your Home Assistant configuration directory. It may need to be created. 152 | 2. Copy the `custom_components/tplink_router` directory into the `custom_components` directory. 153 | 3. Restart Home Assistant. 154 | 155 | ## Configuration 156 | TP-Link Router is configured via the GUI. See [the HA docs](https://www.home-assistant.io/getting-started/integration/) for more details. 157 | 158 | The default data is preset already. 159 | 160 | 161 | 162 | 1. Go to the Settings->Devices & services. 163 | 2. Click on `+ ADD INTEGRATION`, search for `TP-Link Router`. 164 | 3. Fill Password. 165 | 4. Click `SUBMIT` 166 | 167 | NOTE! 168 | 1. If you use `https` connection to your router you may get error `certificate verify failed: EE certificate key too weak`. To fix this - unset `Verify ssl` 169 | 2. If you use `https` connection - You need to turn on "Local Management via HTTPS" (advanced->system->administration) in the router web UI 170 | 3. Use Local Password which is for Log In with Local Password. Login with TP-LINK ID doesnt work 171 | 172 | 173 | 174 | 4. If you got error - `check if the default router username is correct` The default username for most routers is `admin`. Some routers have the default username - `user`. 175 | 5. If you got error - `use web encrypted password instead` Read [web encrypted password](#encrypted_pass) 176 | 6. The TP-Link Web Interface only supports upto 1 user logged in at a time (for security reasons, apparently). So you will be logged out from router web interface when the integration updates data 177 | 178 | ### Web Encrypted Password 179 | If you got error - `use web encrypted password instead. Check the documentation!` 180 | 1. Go to the login page of your router. (default: 192.168.0.1). 181 | 2. Type in the password you use to login into the password field. 182 | 3. Click somewhere else on the page so that the password field is not selected anymore. 183 | 4. Open the JavaScript console of your browser (usually by pressing F12 and then clicking on "Console"). 184 | 5. Type `document.getElementById("login-password").value;` 185 | 6. Copy the returned value as password and use it. 186 | 187 | ### Edit Configuration 188 | You may edit configuration data like: 189 | 1. Router url 190 | 2. Password 191 | 3. Scan interval 192 | 4. Verify https 193 | 194 | To do that: 195 | 196 | 1. Go to the Settings->Devices & services. 197 | 2. Search for `TP-Link Router`, and click on it. 198 | 3. Click on `CONFIGURE` 199 | 4. Edit the options you need and click `SUBMIT` 200 | 201 | ## Supported routers 202 | - [TP-LINK routers](#tplink) 203 | - [MERCUSYS routers](#mercusys) 204 | ### TP-LINK routers 205 | - Archer A6 (2.0, 4.0) 206 | - Archer A7 V5 207 | - Archer A8 (1.0, 2.20) 208 | - Archer A9 V6 209 | - Archer A20 v1.0 210 | - Archer AX10 v1.0 211 | - Archer AX12 v1.0 212 | - Archer AX17 v1.0 213 | - Archer AX20 (v1.0, v3.0) 214 | - Archer AX21 (v1.20, v3.0) 215 | - Archer AX23 (v1.0, v1.2) 216 | - Archer AX50 v1.0 217 | - Archer AX53 (v1.0, v2) 218 | - Archer AX55 (v1.0, V1.60, v4.0) 219 | - Archer AX58 v1.0 220 | - Archer AX72 V1 221 | - Archer AX73 (V1, V2.0) 222 | - Archer AX75 V1 223 | - Archer AX90 V1.20 224 | - Archer AX95 v1.0 225 | - Archer AXE75 V1 226 | - Archer AXE5400 v1.0 227 | - Archer AXE16000 228 | - Archer AX1800 229 | - Archer AX3000 V1 230 | - Archer AX6000 V1 231 | - Archer AX11000 V1 232 | - Archer BE220 v1.0 233 | - Archer BE230 v1.0 234 | - Archer BE400 v1.0 235 | - Archer BE550 v1.0 236 | - Archer BE800 v1.0 237 | - Archer BE805 v1.0 238 | - Archer BE3600 1.6 239 | - Archer C1200 (v1.0, v2.0) 240 | - Archer C2300 (v1.0, v2.0) 241 | - Archer C6 (v2.0, v3.0, v3.20, 4.0) 242 | - Archer C6U v1.0 243 | - Archer C7 (v4.0, v5.0) 244 | - Archer C24 (1.0, 2.0) 245 | - Archer C60 v2.0 246 | - Archer C64 1.0 247 | - Archer C80 (1.0, 2.20) 248 | - Archer C5400X V1 249 | - Archer GX90 v1.0 250 | - Archer MR200 (v2, v5, v5.3, v6.0) 251 | - Archer MR550 v1 252 | - Archer MR600 (v1, v2, v3) 253 | - Archer NX200 v2.0 254 | - Archer VR400 v3 255 | - Archer VR600 v3 256 | - Archer VR900v 257 | - Archer VR1200v v1 258 | - Archer VR2100v v1 259 | - Archer VX231v v1.0 260 | - Archer VX1800v v1.0 261 | - BE11000 2.0 262 | - Deco M4 2.0 263 | - Deco M4R 2.0 264 | - Deco M5 v3 265 | - Deco M9 Pro 266 | - Deco M9 Plus 1.0 267 | - Deco P7 268 | - Deco X20 269 | - Deco X50 v1.3 270 | - Deco X55 1.0 271 | - Deco X60 V3 272 | - Deco X90 273 | - Deco XE75 (v1.0, v2.0) 274 | - Deco XE75PRO (v3.0) 275 | - EX511 v2.0 276 | - HX510 v1.0 277 | - NX510v v1.0 278 | - TD-W9960 (v1, V1.20) 279 | - TL-MR100 v2.0 280 | - TL-MR105 281 | - TL-MR100-Outdoor v1.0 282 | - TL-MR110-Outdoor v1.0 283 | - TL-MR150 v2 284 | - TL-MR6400 (v5, v5.3) 285 | - TL-MR6500v 286 | - TL-WA1201 3.0 287 | - TL-WA3001 v1.0 288 | - TL-XDR3010 V2 289 | - TL-WDR3600 V1 290 | - TL-XDR6088 v1.0.30 291 | - VX420-G2h v1.1 292 | - VX800v v1 293 | - XC220-G3v v2.30 294 | - RE305 4.0 295 | - RE315 1.0 296 | - RE330 v1 297 | ### MERCUSYS routers 298 | - AC10 1.20 299 | - MR47BE v1.0 300 | - MR50G 1.0 301 | - H60XR 1.0 302 | - H47BE 2.0 303 | - Halo H80X 1.0 304 | - Halo H3000x 1.0 305 | 306 | Please let me know if you have tested integration with any other model. Open an issue with info about router's model, hardware and firmware versions. 307 | 308 | ## Adding Support For More Models 309 | Guidelines [CONTRIBUTING.md](https://github.com/AlexandrErohin/TP-Link-Archer-C6U/blob/master/CONTRIBUTING.md) 310 | --------------------------------------------------------------------------------