Compare commits

...

114 commits

Author SHA1 Message Date
Tomaae
e70dcc196d
Updated workflows
Some checks failed
Check for PR merge conflicts / Check for PR merge conflicts (push) Has been cancelled
HACS Action / HACS Action (push) Has been cancelled
SonarCloud / SonarCloud (push) Has been cancelled
CI / Python Code Format Check (push) Has been cancelled
CI / Python Tests (push) Has been cancelled
CI / Security check - Bandit (push) Has been cancelled
CI / Check hassfest (push) Has been cancelled
2025-05-01 14:54:55 +02:00
Tomaae
242f8142d2
Added dhcp client ip to interfaces, ref #321 2025-05-01 14:36:34 +02:00
Tomaae
f9edb6c1fe
Added ip and mac detection for bonds, ref #321 2025-05-01 14:26:10 +02:00
Tomaae
332b1a6657
Added wireless host signal information, fixes #413 2025-05-01 13:19:52 +02:00
Tomaae
477c68cf55
Workaround for 7.17 missing /system/health in some devices, fixes #392 2025-05-01 13:10:17 +02:00
Tomaae
6905bcb6b5
Fixed crash during slow reboot 2025-05-01 12:33:00 +02:00
Tomaae
b15940fc8e
Added more health sensors, fixes #320 2025-05-01 12:11:15 +02:00
Tomaae
a1b2851193
Fixed reboot check, fixes #389 2025-05-01 11:59:18 +02:00
Tomaae
fc51c71323
Update hacs.json 2025-05-01 11:58:23 +02:00
Tomaae
a8547b71dd
Merge remote-tracking branch 'origin/master' 2025-05-01 11:25:19 +02:00
Tomaae
1eefaa89c4
Update hacs.json 2025-05-01 11:25:05 +02:00
Tomaae
6487398741
Reworked scripts to buttons only, fixes #414 2025-05-01 11:21:10 +02:00
Tomaae
ee886e0365
Fixed invalid escape sequence, fixes #401 2025-05-01 10:48:35 +02:00
Tomaae
6f888a5c08
Added config entry migration for SSL Verify option 2025-05-01 10:37:45 +02:00
Tomaae
af01e48c05
Merge pull request #417 from tomaae/lokalise-2025-04-30_13-53-25
Some checks are pending
CI / Check hassfest (push) Blocked by required conditions
CI / Python Code Format Check (push) Waiting to run
CI / Python Tests (push) Waiting to run
CI / Security check - Bandit (push) Blocked by required conditions
Check for PR merge conflicts / Check for PR merge conflicts (push) Waiting to run
HACS Action / HACS Action (push) Waiting to run
SonarCloud / SonarCloud (push) Waiting to run
Lokalise: Translations update
2025-04-30 13:54:47 +02:00
Tomaae
81dc2d8f73 Lokalise: updates 2025-04-30 13:53:37 +02:00
Tomaae
8e68931e57
Added SSL Verify option 2025-04-30 13:35:45 +02:00
Tomaae
955fa33bca
Merge pull request #416 from mvdwetering/fix_invalid_escape_warning
Fix invalid escape sequence warning
2025-04-30 09:05:59 +02:00
Michel van de Wetering
013f946681 Fix wrong typehint 2025-04-25 22:06:20 +02:00
Michel van de Wetering
c26817bd25 Remove codecov, it has been pulled from PyPi and has been completely deprecated it seems 2025-04-25 22:00:18 +02:00
Michel van de Wetering
5650d7f5ca Format with black 2025-04-25 21:49:43 +02:00
Michel van de Wetering
44fe6a626b Use raw string for regex 2025-04-25 21:08:08 +02:00
Tomaae
07f81b887e
Updated librouteros requirement to 3.4.1 2025-02-02 05:16:09 +01:00
Tomaae
339b01dda3
Merge pull request #377 from arfoll/patch-2
Update sensor_types.py: Add system poe out
2025-01-08 01:00:51 +01:00
Tomaae
7bada8570e
Merge pull request #376 from arfoll/patch-1
Update README.md - mention permissions for temp sensors
2025-01-08 00:59:51 +01:00
Tomaae
8ca38e19c0
Merge pull request #370 from lnicolas83/add_phy-temperature
Add health sensor phy-temperature
2025-01-08 00:59:30 +01:00
Tomaae
da2bffcf88
Merge pull request #378 from mvn23/support-713-wifi
Add wifi support for ROS 7.13+ devices without a physical wifi radio
2025-01-08 00:57:51 +01:00
mvn23
464ba7b768 Fix version check for devices without a physical wifi radio 2025-01-03 13:31:10 +01:00
mvn23
cf1b56c51e Add wifi (capsman) support for devices without a physical wifi radio running routeros 7.13+ 2024-10-09 14:02:05 +02:00
Brendan Le Foll
6de1a940ed
Update sensor_types.py: Add system poe out
Add POE total power consumption - at least on CRS328-24P
2024-10-04 16:03:33 +02:00
Brendan Le Foll
1f03cf55dc
Update README.md
Mention that at least on mikrotik 7.16 you need also reboot permissions in order for the health attributes to be shown. Unclear if this is a bug or not.
2024-10-04 15:51:45 +02:00
Nicolas Lisoski
b3aa0e3d90 Add sensor phy-temperature 2024-07-19 10:28:53 +02:00
Tomaae
de1ca6a6f6
Change board name for non mikrotik hardware #363 2024-07-06 15:23:57 +02:00
Tomaae
890dd11e8b
Merge pull request #354 from skrashevich/fix-changelog
Fix fetching changelogs
2024-04-25 10:20:07 +02:00
Tomaae
b2a77d2de3
Update hacs.json 2024-04-25 10:13:29 +02:00
Sergey Krashevich
502bebb766
fix(mikrotik_router): ensure proper spacing in combined changelogs 2024-04-19 20:45:21 +03:00
Sergey Krashevich
b9d87cd6aa
feat(mikrotik_router): fetch and concatenate changelogs for updates in reverse order 2024-04-16 18:31:35 +03:00
Sergey Krashevich
b6c89fb152
refactor(mikrotik_router): remove unused aiohttp import from update module 2024-04-16 18:03:00 +03:00
Sergey Krashevich
2aa2191334
refactor(mikrotik_router): use existing session for fetching release notes
Optimized the process of fetching release notes in the Mikrotik Router integration by utilizing the existing HTTP client session from Home Assistant's aiohttp_client. This change avoids creating a new session for each request, leading to more efficient resource usage and potentially reducing the likelihood of encountering issues related to session management.

By leveraging `async_get_clientsession`, the update component now aligns better with Home Assistant's recommended practices for external HTTP requests, ensuring consistency and reliability in how network calls are made within integrations.
2024-04-16 17:58:26 +03:00
Sergey Krashevich
ccc097c438
feat(mikrotik_router): switch to aiohttp for async release notes fetching
This commit replaces the synchronous requests library with aiohttp for asynchronous fetching of Mikrotik RouterOS update release notes. It introduces the use of Home Assistant's built-in `async_create_clientsession` to manage HTTP sessions, enhancing the integration's performance and reliability by leveraging asyncio for network operations.

The change aims to improve the responsiveness and efficiency of the Mikrotik Router integration within Home Assistant, particularly in retrieving release notes for updates. By moving to an asynchronous model, the integration can now fetch data without blocking the execution of other tasks, leading to a smoother user experience.

Additionally, the error handling has been updated to provide clearer messages in the log when the release notes cannot be fetched, either due to a network error or an exception. This improvement aids in troubleshooting and ensures that users are better informed about the status of their update checks.
2024-04-16 17:57:02 +03:00
Sergey Krashevich
8e8ff310c1
feat(mikrotik_router): format release notes for better readability
This commit updates the handling of MikroTik RouterOS update release notes within the custom component. Specifically, it modifies the formatting of the release notes text by replacing instances of "*) " with "- ", in addition to converting newline characters to "<br />" HTML tags. This change aims to improve the readability of the release notes when displayed in the UI, making it easier for users to understand the changes in each RouterOS update.
2024-04-16 17:57:01 +03:00
Sergey Krashevich
04b0987781
feat(update): update Mikrotik RouterOS changelog URL
This commit updates the URL used to fetch the Mikrotik RouterOS changelog. Previously, the integration fetched the changelog from a general Mikrotik changelogs page, which required specifying the version as a parameter. This approach has been changed to directly access the changelog from the CDN for the specific RouterOS version in question.

The change ensures that the integration directly accesses the relevant changelog file for the specified RouterOS version, potentially improving reliability and speed of access to these changelogs. This modification is particularly useful for environments where RouterOS updates are closely monitored, and up-to-date information is critical for maintaining system security and performance.
2024-04-16 17:55:55 +03:00
Tomaae
802c218bc9
Update stale.yml 2024-03-31 14:00:15 +02:00
Tomaae
c02162dfef
Update hacs.json 2024-02-17 00:20:31 +01:00
Tomaae
50abfe86b6
Update coordinator.py
Futureproof wifi module 7.13 change
2024-02-14 22:05:09 +01:00
Tomaae
5feb9ad3c2
Merge pull request #332 from slackr31337/ros-7.13
Fix for ros 7.13 wireless
2024-02-14 22:02:34 +01:00
Robert Dunmire III
6bc441558f Update coordinator.py 2023-12-27 09:28:17 -05:00
Robert Dunmire III
e828e0bf0a Fixes from code review 2023-12-16 12:57:01 -05:00
Robert Dunmire III
1bbedfaa8b Fix for ros 7.13 wireless 2023-12-08 10:30:15 -05:00
Tomaae
a61dfa5ff7
fixed uom for interfaces, fixes #325 2023-12-07 00:54:23 +01:00
Tomaae
80bae0c34d
black 2023-11-06 00:48:47 +01:00
Tomaae
593aca593b
Merge pull request #316 from L2jLiga/master
feat: netwatch binary sensor
2023-10-23 23:21:39 +02:00
Andrey Chalkin
13e765c10a
feat: netwatch binary sensor 2023-10-15 17:16:33 +02:00
Tomaae
2cdd645274
removed forgotten dev print 2023-10-06 02:07:51 +02:00
Tomaae
355d16ff4f
Merge remote-tracking branch 'origin/master' 2023-09-18 09:28:19 +02:00
Tomaae
e5f0e40832
Merge pull request #307 from tomaae/lokalise-2023-09-18_09-26-54
Lokalise: Translations update
2023-09-18 09:28:08 +02:00
Tomaae
5809d0aad9 Lokalise: updates 2023-09-18 09:27:03 +02:00
Tomaae
da894aa32a
Fixed MikrotikOS v6 system health sensor default values, ref #300 2023-09-18 09:19:07 +02:00
Tomaae
975f882416
Added query path into query error 2023-09-18 09:07:10 +02:00
Tomaae
715ead5458
Fixed diagnostics, fixes #303 2023-09-18 08:52:37 +02:00
Tomaae
4b610ba4e5
Fixed traffic sensor names, ref #306 2023-09-18 08:48:45 +02:00
Tomaae
6079affcfa
Fixed device tracker names 2023-09-12 12:00:42 +02:00
Tomaae
d3a2432eb7
Fixed entity names 2023-09-12 11:49:27 +02:00
Tomaae
2349584551
Do not process device tracker when tracking coordinator is disabled 2023-09-12 11:22:01 +02:00
Tomaae
4abf5b210d
added Callable definition 2023-09-12 11:13:53 +02:00
Tomaae
380884d60a
Merge pull request #296 from tomaae/coordinator
Coordinator
2023-08-31 02:54:27 +02:00
Tomaae
c8728238af
rebase 2023-08-31 02:53:34 +02:00
Tomaae
e1e9f61e08
cleanup 2023-08-10 02:21:16 +02:00
Tomaae
9c5a270b4f
cleanup 2023-08-10 02:03:11 +02:00
Tomaae
efe3de0c20
added coordinator for device tracker 2023-08-09 23:00:00 +02:00
Tomaae
ebee5a34e2
reverted to standard scan interval 2023-08-09 13:55:53 +02:00
Tomaae
ca58ee6007
fixed traffic uom 2023-08-09 13:55:31 +02:00
Tomaae
04e0d875cf
fixed client traffic sensors 2023-08-09 11:54:08 +02:00
Tomaae
51c9c62859
fixed "Ignoring invalid device info" error from 2023.8, fixes #292 2023-08-09 11:33:38 +02:00
Tomaae
5186956883
updated minimum ha version 2023-08-09 11:03:29 +02:00
Tomaae
c83e735724
merged hwinfo and fwupdate_check into main update loop 2023-08-09 10:49:49 +02:00
Tomaae
b8f4856a5b
converted device tracker 2023-08-09 10:19:04 +02:00
Tomaae
514a930d04
fixed firmware update sensor 2023-08-09 10:18:46 +02:00
Tomaae
6caaa4e891
converted buttons 2023-08-09 09:59:50 +02:00
Tomaae
15bc156d74
converted update sensors 2023-08-09 09:53:48 +02:00
Tomaae
eae9fd7e2d
fixed switch control 2023-08-09 09:48:08 +02:00
Tomaae
727a075dbb
converted switches 2023-08-09 09:37:48 +02:00
Tomaae
2ea35dec75
converted binary sensors 2023-08-09 09:33:52 +02:00
Tomaae
cace2ae3e5
removed unnecessery MikrotikClientTrafficSensor 2023-08-09 09:25:33 +02:00
Tomaae
7a892b6800
cleanup 2023-08-09 02:33:49 +02:00
Tomaae
3869e670a6
removed available override 2023-08-09 02:32:35 +02:00
Tomaae
7c2e61e4d6
Removed asyncio locks 2023-08-09 02:24:49 +02:00
Tomaae
009d8c6085
removed async_added/removed_to_hass override 2023-08-09 02:24:20 +02:00
Tomaae
a21e95c36a
coordinator update 2023-08-08 00:50:09 +02:00
Tomaae
4d957aec22
Merge pull request #285 from ivanpavlina/master
Support for additional attributes on execute
2023-05-31 16:44:08 +02:00
Ivan Pavlina
d8d8552dd0 black formatting 2023-05-31 07:46:52 +00:00
Ivan Pavlina
22844e7ae5 added attributes to execute method, using it on check-for-updates. hardcoded 10 seconds for duration 2023-05-31 06:56:50 +00:00
Tomaae
251874bdc2
Merge pull request #283 from 910patin/master
add SensorStateClass.MEASUREMENT for client counts
2023-05-27 17:46:47 +02:00
910patin
52eeb08c43
add SensorStateClass.MEASUREMENT for client counts 2023-05-27 16:46:49 +03:00
Tomaae
b471038cb3
Merge pull request #279 from tomaae/lokalise-2023-04-17_14-57-33
Lokalise: Translations update
2023-04-17 14:59:09 +02:00
Tomaae
053dcf0354 Lokalise: updates 2023-04-17 14:57:41 +02:00
Tomaae
97d5a80387
Update README.md 2023-02-24 14:59:03 +01:00
Tomaae
ce9ce906f5
Do not update IP or hostname from disabled DHCP entry #265 2023-01-20 09:35:59 +01:00
Tomaae
a7c00d8fb3
fixed typo #265 2023-01-18 23:51:22 +01:00
Tomaae
18e0359490
ignore disabled DHCP entries for host tracking #265 2023-01-17 23:45:16 +01:00
Tomaae
e68bbec8dc
Update ci.yml 2023-01-16 01:54:26 +01:00
Tomaae
4405e7d348
Update ci.yml 2023-01-16 01:52:00 +01:00
Tomaae
bb8a14999e
Update ci.yml 2023-01-16 01:49:54 +01:00
Tomaae
7b8fe02c7e
Update setup.cfg 2023-01-16 01:36:09 +01:00
Tomaae
267fb6a011
Update ci.yml 2023-01-16 01:29:48 +01:00
Tomaae
10aeb0ba1b
Update setup.cfg 2023-01-16 01:25:35 +01:00
Tomaae
f0601f7c19
Update ci.yml 2023-01-16 01:21:37 +01:00
Tomaae
d49aaa7af4
Merge pull request #266 from mvdwetering/next_button_for_first_step_in_optionsflow
Next button for first step in optionsflow
2023-01-16 01:02:48 +01:00
Michel van de Wetering
cb4c5edd26 Next button for first step in optionsflow 2023-01-15 15:21:17 +01:00
Tomaae
bd888c12ba
added blocked attribute to kid control #247 2022-09-30 00:36:55 +02:00
Tomaae
0737a93c2c
increased update check time 2022-09-06 07:08:43 +02:00
Tomaae
fbbf4d8e33
added switch temperature sensor #243 2022-09-06 07:05:45 +02:00
Tomaae
385092a513
added "test" permission check #244 2022-09-05 21:11:03 +02:00
Tomaae
fd0689bf4c
updated docs #244 2022-09-05 21:09:15 +02:00
59 changed files with 2428 additions and 1742 deletions

View file

@ -10,6 +10,7 @@ on:
paths:
- 'custom_components/**'
- 'tests/**'
workflow_dispatch:
jobs:
@ -18,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: Black Code Format Check
uses: lgeiger/black-action@master
with:
@ -29,11 +30,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: "actions/checkout@v2"
- name: Set up Python 3.9
uses: actions/setup-python@v1
uses: "actions/checkout@v3"
- name: Set up Python 3.13
uses: actions/setup-python@v4
with:
python-version: 3.9
python-version: '3.13'
- name: Generate Requirements lists
run: |
python3 .github/generate_requirements.py
@ -45,9 +46,7 @@ jobs:
- name: Lint with flake8
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --ignore W503,E722,F722 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=15 --max-line-length=127 --statistics
# - name: Test with pytest
# run: |
@ -60,7 +59,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code from GitHub
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: Security check - Bandit
uses: ioggstream/bandit-report-artifacts@v0.0.2
with:
@ -68,7 +67,7 @@ jobs:
config_file: .github/bandit.yaml
ignore_failure: false
- name: Security check report artifacts
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v3
with:
name: Security report
path: output/security_report.txt
@ -79,6 +78,6 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: Check out code from GitHub
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: Run hassfest
uses: home-assistant/actions/hassfest@master

View file

@ -34,10 +34,10 @@ jobs:
- name: Check out repository
uses: actions/checkout@v2
- name: Set up Python 3.8
- name: Set up Python 3.13
uses: actions/setup-python@v1
with:
python-version: 3.8
python-version: 3.13
- name: Install requirements
run: |

View file

@ -9,7 +9,7 @@ jobs:
name: Stale
runs-on: ubuntu-latest
steps:
- uses: "actions/stale@v5.1.1"
- uses: "actions/stale@v9"
with:
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'
stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'
@ -17,4 +17,4 @@ jobs:
days-before-stale: 14
days-before-close: 7
exempt-issue-labels: 'pinned,security,planned,help wanted'
exempt-pr-labels: 'pinned,security,planned,help wanted'
exempt-pr-labels: 'pinned,security,planned,help wanted'

View file

@ -8,7 +8,6 @@ wheel = ">=0.34"
pygithub = ">=1.47"
homeassistant = ">=2021.4.6"
sqlalchemy = "==1.3.16"
codecov = "==2.0.15"
mock-open = "==1.3.1"
mypy = "==0.770"
pre-commit = "==2.2.0"
@ -24,5 +23,5 @@ requests_mock = "==1.7.0"
responses = "==0.10.6"
[packages]
librouteros = ">=3.2.0"
librouteros = ">=3.4.1"
mac-vendor-lookup = ">=0.1.12"

View file

@ -6,7 +6,7 @@
![GitHub commits since latest release](https://img.shields.io/github/commits-since/tomaae/homeassistant-mikrotik_router/latest?style=plastic)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/tomaae/homeassistant-mikrotik_router?style=plastic)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/tomaae/homeassistant-mikrotik_router/CI?label=CI&style=plastic)
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/tomaae/homeassistant-mikrotik_router/ci.yml?style=plastic)
[![Help localize](https://img.shields.io/badge/lokalise-join-green?style=plastic&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REVCNzgzOEY4NDYxMTFFQUIyMEY4Njc0NzVDOUZFMkMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REVCNzgzOEU4NDYxMTFFQUIyMEY4Njc0NzVDOUZFMkMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTcgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozN0ZDRUY4Rjc0M0UxMUU3QUQ2MDg4M0Q0MkE0NjNCNSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozN0ZDRUY5MDc0M0UxMUU3QUQ2MDg4M0Q0MkE0NjNCNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pjs1zyIAAABVSURBVHjaYvz//z8DOYCJgUxAtkYW9+mXyXIrI7l+ZGHc0k5nGxkupdHZxve1yQR1CjbPZURXh9dGoGJZIPUI2QC4JEgjIfyuJuk/uhgj3dMqQIABAPEGTZ/+h0kEAAAAAElFTkSuQmCC)](https://app.lokalise.com/public/581188395e9778a6060128.17699416/)
@ -106,6 +106,11 @@ Track availability of all network devices. All devices visible to Mikrotik devic
![Host tracker](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/host_tracker.png)
## Netwatch Tracking
Track netwatch status.
![Netwatch](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/netwatch_tracker.png)
## Scripts
Execute Mikrotik Router scripts.
You can execute scripts by automatically created switches or using services.
@ -174,9 +179,9 @@ Use integration master branch instead of latest release to keep up with RouterOS
## Setup integration
1. Create user for homeassistant on your mikrotik router with following permissions:
* read, write, api, reboot, policy
* read, write, api, reboot, policy, test
* lower permissions are supported, but it will limit functionality (read and api permissions are mandatory).
* system health sensors won't be available without write permissions. this limitation is on mikrotik side.
* system health sensors won't be available without write & reboot permissions. this limitation is on mikrotik side.
2. If you want to be able to execute scripts on your mikrotik router from HA, script needs to have only following policies:
* read, write
or check "Don't Require Permissions" option

View file

@ -1,81 +1,65 @@
"""Mikrotik Router integration."""
import voluptuous as vol
from __future__ import annotations
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
import logging
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import device_registry
from homeassistant.config_entries import ConfigEntry
from homeassistant.exceptions import ConfigEntryNotReady
from .const import (
PLATFORMS,
DOMAIN,
RUN_SCRIPT_COMMAND,
)
from .mikrotik_controller import MikrotikControllerData
from homeassistant.const import CONF_VERIFY_SSL
from .const import PLATFORMS, DOMAIN, DEFAULT_VERIFY_SSL
from .coordinator import MikrotikData, MikrotikCoordinator, MikrotikTrackerCoordinator
SCRIPT_SCHEMA = vol.Schema(
{vol.Required("router"): cv.string, vol.Required("script"): cv.string}
)
# ---------------------------
# async_setup
# ---------------------------
async def async_setup(hass, _config):
"""Set up configured Mikrotik Controller."""
hass.data[DOMAIN] = {}
return True
# ---------------------------
# update_listener
# ---------------------------
async def update_listener(hass, config_entry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(config_entry.entry_id)
_LOGGER = logging.getLogger(__name__)
# ---------------------------
# async_setup_entry
# ---------------------------
async def async_setup_entry(hass, config_entry) -> bool:
"""Set up Mikrotik Router as config entry."""
controller = MikrotikControllerData(hass, config_entry)
await controller.async_hwinfo_update()
if not controller.connected():
raise ConfigEntryNotReady("Cannot connect to host")
await controller.async_update()
if not controller.data:
raise ConfigEntryNotReady()
await controller.async_init()
hass.data[DOMAIN][config_entry.entry_id] = controller
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Set up a config entry."""
coordinator = MikrotikCoordinator(hass, config_entry)
await coordinator.async_config_entry_first_refresh()
coordinatorTracker = MikrotikTrackerCoordinator(hass, config_entry, coordinator)
await coordinatorTracker.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = MikrotikData(
data_coordinator=coordinator,
tracker_coordinator=coordinatorTracker,
)
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
config_entry.async_on_unload(config_entry.add_update_listener(update_listener))
hass.services.async_register(
DOMAIN, RUN_SCRIPT_COMMAND, controller.run_script, schema=SCRIPT_SCHEMA
)
config_entry.async_on_unload(config_entry.add_update_listener(async_reload_entry))
return True
# ---------------------------
# async_reload_entry
# ---------------------------
async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Reload the config entry when it changed."""
await hass.config_entries.async_reload(config_entry.entry_id)
# ---------------------------
# async_unload_entry
# ---------------------------
async def async_unload_entry(hass, config_entry) -> bool:
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(
if unload_ok := await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
if unload_ok:
controller = hass.data[DOMAIN][config_entry.entry_id]
await controller.async_reset()
hass.services.async_remove(DOMAIN, RUN_SCRIPT_COMMAND)
):
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
@ -89,3 +73,26 @@ async def async_remove_config_entry_device(
) -> bool:
"""Remove a config entry from a device."""
return True
# ---------------------------
# async_migrate_entry
# ---------------------------
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
_LOGGER.debug(
"Migrating configuration from version %s.%s",
config_entry.version,
config_entry.minor_version,
)
if config_entry.version < 2:
new_data = {**config_entry.data}
new_data[CONF_VERIFY_SSL] = DEFAULT_VERIFY_SSL
hass.config_entries.async_update_entry(config_entry, data=new_data, version=2)
_LOGGER.debug(
"Migration to configuration version %s.%s successful",
config_entry.version,
config_entry.minor_version,
)
return True

View file

@ -1,9 +1,13 @@
"""API parser for JSON APIs"""
from pytz import utc
from logging import getLogger
"""API parser for JSON APIs."""
from datetime import datetime
from logging import getLogger
from pytz import utc
from voluptuous import Optional
from homeassistant.components.diagnostics import async_redact_data
from .const import TO_REDACT
_LOGGER = getLogger(__name__)
@ -13,7 +17,7 @@ _LOGGER = getLogger(__name__)
# utc_from_timestamp
# ---------------------------
def utc_from_timestamp(timestamp: float) -> datetime:
"""Return a UTC time from a timestamp"""
"""Return a UTC time from a timestamp."""
return utc.localize(datetime.utcfromtimestamp(timestamp))
@ -21,7 +25,7 @@ def utc_from_timestamp(timestamp: float) -> datetime:
# from_entry
# ---------------------------
def from_entry(entry, param, default="") -> str:
"""Validate and return str value an API dict"""
"""Validate and return str value an API dict."""
if "/" in param:
for tmp_param in param.split("/"):
if isinstance(entry, dict) and tmp_param in entry:
@ -50,7 +54,7 @@ def from_entry(entry, param, default="") -> str:
# from_entry_bool
# ---------------------------
def from_entry_bool(entry, param, default=False, reverse=False) -> bool:
"""Validate and return a bool value from an API dict"""
"""Validate and return a bool value from an API dict."""
if "/" in param:
for tmp_param in param.split("/"):
if isinstance(entry, dict) and tmp_param in entry:
@ -91,8 +95,11 @@ def parse_api(
only=None,
skip=None,
) -> dict:
"""Get data from API"""
"""Get data from API."""
debug = _LOGGER.getEffectiveLevel() == 10
if type(source) == dict:
tmp = source
source = [tmp]
if not source:
if not key and not key_search:
@ -138,7 +145,7 @@ def parse_api(
# get_uid
# ---------------------------
def get_uid(entry, key, key_secondary, key_search, keymap) -> Optional(str):
"""Get UID for data list"""
"""Get UID for data list."""
uid = None
if not key_search:
key_primary_found = key in entry
@ -167,7 +174,7 @@ def get_uid(entry, key, key_secondary, key_search, keymap) -> Optional(str):
# generate_keymap
# ---------------------------
def generate_keymap(data, key_search) -> Optional(dict):
"""Generate keymap"""
"""Generate keymap."""
return (
{data[uid][key_search]: uid for uid in data if key_search in data[uid]}
if key_search
@ -179,7 +186,7 @@ def generate_keymap(data, key_search) -> Optional(dict):
# matches_only
# ---------------------------
def matches_only(entry, only) -> bool:
"""Return True if all variables are matched"""
"""Return True if all variables are matched."""
ret = False
for val in only:
if val["key"] in entry and entry[val["key"]] == val["value"]:
@ -195,7 +202,7 @@ def matches_only(entry, only) -> bool:
# can_skip
# ---------------------------
def can_skip(entry, skip) -> bool:
"""Return True if at least one variable matches"""
"""Return True if at least one variable matches."""
ret = False
for val in skip:
if val["name"] in entry and entry[val["name"]] == val["value"]:
@ -213,7 +220,7 @@ def can_skip(entry, skip) -> bool:
# fill_defaults
# ---------------------------
def fill_defaults(data, vals) -> dict:
"""Fill defaults if source is not present"""
"""Fill defaults if source is not present."""
for val in vals:
_name = val["name"]
_type = val["type"] if "type" in val else "str"
@ -242,7 +249,7 @@ def fill_defaults(data, vals) -> dict:
# fill_vals
# ---------------------------
def fill_vals(data, entry, uid, vals) -> dict:
"""Fill all data"""
"""Fill all data."""
for val in vals:
_name = val["name"]
_type = val["type"] if "type" in val else "str"
@ -292,7 +299,7 @@ def fill_vals(data, entry, uid, vals) -> dict:
# fill_ensure_vals
# ---------------------------
def fill_ensure_vals(data, uid, ensure_vals) -> dict:
"""Add required keys which are not available in data"""
"""Add required keys which are not available in data."""
for val in ensure_vals:
if uid:
if val["name"] not in data[uid]:
@ -310,7 +317,7 @@ def fill_ensure_vals(data, uid, ensure_vals) -> dict:
# fill_vals_proc
# ---------------------------
def fill_vals_proc(data, uid, vals_proc) -> dict:
"""Add custom keys"""
"""Add custom keys."""
_data = data[uid] if uid else data
for val_sub in vals_proc:
_name = None

View file

@ -1,48 +1,53 @@
"""Support for the Mikrotik Router binary sensor service."""
import logging
from typing import Any
from __future__ import annotations
from logging import getLogger
from collections.abc import Mapping
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
)
from .helper import format_attribute
from .const import (
CONF_SENSOR_PPP,
DEFAULT_SENSOR_PPP,
CONF_SENSOR_PORT_TRACKER,
DEFAULT_SENSOR_PORT_TRACKER,
)
from .model import model_async_setup_entry, MikrotikEntity
from typing import Any
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .binary_sensor_types import (
SENSOR_TYPES,
SENSOR_SERVICES,
DEVICE_ATTRIBUTES_IFACE_ETHER,
DEVICE_ATTRIBUTES_IFACE_SFP,
DEVICE_ATTRIBUTES_IFACE_WIRELESS,
DEVICE_ATTRIBUTES_NETWATCH,
)
from .const import (
CONF_SENSOR_PPP,
DEFAULT_SENSOR_PPP,
CONF_SENSOR_PORT_TRACKER,
DEFAULT_SENSOR_PORT_TRACKER,
CONF_SENSOR_NETWATCH_TRACKER,
DEFAULT_SENSOR_NETWATCH_TRACKER,
)
from .entity import MikrotikEntity, async_add_entities
from .helper import format_attribute
_LOGGER = logging.getLogger(__name__)
_LOGGER = getLogger(__name__)
# ---------------------------
# async_setup_entry
# ---------------------------
async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
_async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry for component"""
dispatcher = {
"MikrotikBinarySensor": MikrotikBinarySensor,
"MikrotikPPPSecretBinarySensor": MikrotikPPPSecretBinarySensor,
"MikrotikPortBinarySensor": MikrotikPortBinarySensor,
}
await model_async_setup_entry(
hass,
config_entry,
async_add_entities,
SENSOR_SERVICES,
SENSOR_TYPES,
dispatcher,
)
await async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
@ -86,10 +91,10 @@ class MikrotikPPPSecretBinarySensor(MikrotikBinarySensor):
else False
)
@property
def available(self) -> bool:
"""Return if controller is available."""
return self._ctrl.connected() if self.option_sensor_ppp else False
# @property
# def available(self) -> bool:
# """Return if controller is available."""
# return self._ctrl.connected() if self.option_sensor_ppp else False
# ---------------------------
@ -105,10 +110,10 @@ class MikrotikPortBinarySensor(MikrotikBinarySensor):
CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER
)
@property
def available(self) -> bool:
"""Return if controller is available."""
return self._ctrl.connected() if self.option_sensor_port_tracker else False
# @property
# def available(self) -> bool:
# """Return if controller is available."""
# return self._ctrl.connected() if self.option_sensor_port_tracker else False
@property
def icon(self) -> str:

View file

@ -1,4 +1,7 @@
"""Definitions for Mikrotik Router binary sensor entities."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
@ -106,28 +109,38 @@ DEVICE_ATTRIBUTES_UPS = [
"hid-self-test",
]
DEVICE_ATTRIBUTES_NETWATCH = [
"host",
"type",
"interval",
"port",
"http-codes",
"status",
"comment",
]
@dataclass
class MikrotikBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Class describing mikrotik entities."""
icon_enabled: str = ""
icon_disabled: str = ""
ha_group: str = ""
ha_connection: str = ""
ha_connection_value: str = ""
data_path: str = ""
icon_enabled: str | None = None
icon_disabled: str | None = None
ha_group: str | None = None
ha_connection: str | None = None
ha_connection_value: str | None = None
data_path: str | None = None
data_attribute: str = "available"
data_name: str = ""
data_name: str | None = None
data_name_comment: bool = False
data_uid: str = ""
data_reference: str = ""
data_uid: str | None = None
data_reference: str | None = None
data_attributes_list: List = field(default_factory=lambda: [])
func: str = "MikrotikBinarySensor"
SENSOR_TYPES = {
"system_ups": MikrotikBinarySensorEntityDescription(
SENSOR_TYPES: tuple[BinarySensorEntityDescription, ...] = (
MikrotikBinarySensorEntityDescription(
key="system_ups",
name="UPS",
icon_enabled="",
@ -141,7 +154,7 @@ SENSOR_TYPES = {
data_reference="",
data_attributes_list=DEVICE_ATTRIBUTES_UPS,
),
"ppp_tracker": MikrotikBinarySensorEntityDescription(
MikrotikBinarySensorEntityDescription(
key="ppp_tracker",
name="PPP",
icon_enabled="mdi:account-network-outline",
@ -158,7 +171,7 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_PPP_SECRET,
func="MikrotikPPPSecretBinarySensor",
),
"interface": MikrotikBinarySensorEntityDescription(
MikrotikBinarySensorEntityDescription(
key="interface",
name="Connection",
icon_enabled="mdi:lan-connect",
@ -175,6 +188,24 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikPortBinarySensor",
),
}
MikrotikBinarySensorEntityDescription(
key="netwatch",
name="Netwatch",
icon_enabled="mdi:lan-connect",
icon_disabled="mdi:lan-pending",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
ha_group="Netwatch",
ha_connection=DOMAIN,
ha_connection_value="Netwatch",
data_path="netwatch",
data_attribute="status",
data_name="host",
data_name_comment=True,
data_uid="host",
data_reference="host",
data_attributes_list=DEVICE_ATTRIBUTES_NETWATCH,
func="MikrotikBinarySensor",
),
)
SENSOR_SERVICES = {}

View file

@ -1,33 +1,38 @@
"""Support for the Mikrotik Router buttons."""
import logging
from __future__ import annotations
from logging import getLogger
from homeassistant.components.button import ButtonEntity
from .model import model_async_setup_entry, MikrotikEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .entity import MikrotikEntity, async_add_entities
from .button_types import (
SENSOR_TYPES,
SENSOR_SERVICES,
)
from .exceptions import ApiEntryNotFound
_LOGGER = logging.getLogger(__name__)
_LOGGER = getLogger(__name__)
# ---------------------------
# async_setup_entry
# ---------------------------
async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
_async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry for component"""
dispatcher = {
"MikrotikButton": MikrotikButton,
"MikrotikScriptButton": MikrotikScriptButton,
}
await model_async_setup_entry(
hass,
config_entry,
async_add_entities,
SENSOR_SERVICES,
SENSOR_TYPES,
dispatcher,
)
await async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
@ -50,6 +55,8 @@ class MikrotikScriptButton(MikrotikButton):
"""Representation of a script button."""
async def async_press(self) -> None:
"""Process the button press."""
self._ctrl.run_script(self._data["name"])
await self._ctrl.force_update()
"""Run script using Mikrotik API"""
try:
self.coordinator.api.run_script(self._data["name"])
except ApiEntryNotFound as error:
_LOGGER.error("Failed to run script: %s", error)

View file

@ -1,11 +1,15 @@
"""Definitions for Mikrotik Router button entities."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from homeassistant.components.sensor import (
SensorEntityDescription,
)
from .const import DOMAIN
from .const import DOMAIN
DEVICE_ATTRIBUTES_SCRIPT = [
"last-started",
@ -17,21 +21,21 @@ DEVICE_ATTRIBUTES_SCRIPT = [
class MikrotikButtonEntityDescription(SensorEntityDescription):
"""Class describing mikrotik entities."""
ha_group: str = ""
ha_connection: str = ""
ha_connection_value: str = ""
data_path: str = ""
data_attribute: str = ""
data_name: str = ""
ha_group: str | None = None
ha_connection: str | None = None
ha_connection_value: str | None = None
data_path: str | None = None
data_attribute: str | None = None
data_name: str | None = None
data_name_comment: bool = False
data_uid: str = ""
data_reference: str = ""
data_uid: str | None = None
data_reference: str | None = None
data_attributes_list: List = field(default_factory=lambda: [])
func: str = "MikrotikButton"
SENSOR_TYPES = {
"script": MikrotikButtonEntityDescription(
SENSOR_TYPES: tuple[MikrotikButtonEntityDescription, ...] = (
MikrotikButtonEntityDescription(
key="script",
name="",
icon="mdi:script-text-outline",
@ -47,6 +51,6 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_SCRIPT,
func="MikrotikScriptButton",
),
}
)
SENSOR_SERVICES = {}

View file

@ -12,10 +12,10 @@ from homeassistant.const import (
CONF_NAME,
CONF_HOST,
CONF_PORT,
CONF_UNIT_OF_MEASUREMENT,
CONF_USERNAME,
CONF_PASSWORD,
CONF_SSL,
CONF_VERIFY_SSL,
CONF_ZONE,
STATE_HOME,
)
@ -55,13 +55,14 @@ from .const import (
DEFAULT_SENSOR_ENVIRONMENT,
CONF_TRACK_HOSTS_TIMEOUT,
DEFAULT_TRACK_HOST_TIMEOUT,
LIST_UNIT_OF_MEASUREMENT,
DEFAULT_UNIT_OF_MEASUREMENT,
DEFAULT_HOST,
DEFAULT_USERNAME,
DEFAULT_PORT,
DEFAULT_DEVICE_NAME,
DEFAULT_SSL,
DEFAULT_VERIFY_SSL,
DEFAULT_SENSOR_NETWATCH_TRACKER,
CONF_SENSOR_NETWATCH_TRACKER,
)
from .mikrotikapi import MikrotikAPI
@ -85,7 +86,7 @@ def configured_instances(hass):
class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
"""MikrotikControllerConfigFlow class"""
VERSION = 1
VERSION = 2
CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL
def __init__(self):
@ -116,6 +117,7 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
password=user_input[CONF_PASSWORD],
port=user_input[CONF_PORT],
use_ssl=user_input[CONF_SSL],
ssl_verify=user_input[CONF_VERIFY_SSL],
)
if not api.connect():
errors[CONF_HOST] = api.error
@ -136,6 +138,7 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_PASSWORD: DEFAULT_USERNAME,
CONF_PORT: DEFAULT_PORT,
CONF_SSL: DEFAULT_SSL,
CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL,
},
errors=errors,
)
@ -155,6 +158,9 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
vol.Required(CONF_PASSWORD, default=user_input[CONF_PASSWORD]): str,
vol.Optional(CONF_PORT, default=user_input[CONF_PORT]): int,
vol.Optional(CONF_SSL, default=user_input[CONF_SSL]): bool,
vol.Optional(
CONF_VERIFY_SSL, default=user_input[CONF_VERIFY_SSL]
): bool,
}
),
errors=errors,
@ -184,6 +190,7 @@ class MikrotikControllerOptionsFlowHandler(OptionsFlow):
return self.async_show_form(
step_id="basic_options",
last_step=False,
data_schema=vol.Schema(
{
vol.Optional(
@ -192,12 +199,6 @@ class MikrotikControllerOptionsFlowHandler(OptionsFlow):
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
): int,
vol.Optional(
CONF_UNIT_OF_MEASUREMENT,
default=self.config_entry.options.get(
CONF_UNIT_OF_MEASUREMENT, DEFAULT_UNIT_OF_MEASUREMENT
),
): vol.In(LIST_UNIT_OF_MEASUREMENT),
vol.Optional(
CONF_TRACK_IFACE_CLIENTS,
default=self.config_entry.options.get(
@ -288,6 +289,13 @@ class MikrotikControllerOptionsFlowHandler(OptionsFlow):
CONF_SENSOR_KIDCONTROL, DEFAULT_SENSOR_KIDCONTROL
),
): bool,
vol.Optional(
CONF_SENSOR_NETWATCH_TRACKER,
default=self.config_entry.options.get(
CONF_SENSOR_NETWATCH_TRACKER,
DEFAULT_SENSOR_NETWATCH_TRACKER,
),
): bool,
vol.Optional(
CONF_SENSOR_PPP,
default=self.config_entry.options.get(

View file

@ -1,94 +1,94 @@
"""Constants used by the Mikrotik Router component and platforms."""
from homeassistant.const import Platform
PLATFORMS = [
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.DEVICE_TRACKER,
Platform.SWITCH,
Platform.BUTTON,
Platform.UPDATE,
]
DOMAIN = "mikrotik_router"
DEFAULT_NAME = "Mikrotik Router"
ATTRIBUTION = "Data provided by Mikrotik"
RUN_SCRIPT_COMMAND = "run_script"
DEFAULT_ENCODING = "ISO-8859-1"
DEFAULT_LOGIN_METHOD = "plain"
DEFAULT_HOST = "10.0.0.1"
DEFAULT_USERNAME = "admin"
DEFAULT_PORT = 0
DEFAULT_DEVICE_NAME = "Mikrotik"
DEFAULT_SSL = False
CONF_SCAN_INTERVAL = "scan_interval"
DEFAULT_SCAN_INTERVAL = 30
LIST_UNIT_OF_MEASUREMENT = ["bps", "Kbps", "Mbps", "B/s", "KB/s", "MB/s"]
DEFAULT_UNIT_OF_MEASUREMENT = "Kbps"
CONF_TRACK_IFACE_CLIENTS = "track_iface_clients"
DEFAULT_TRACK_IFACE_CLIENTS = True
CONF_TRACK_HOSTS = "track_network_hosts"
DEFAULT_TRACK_HOSTS = False
CONF_TRACK_HOSTS_TIMEOUT = "track_network_hosts_timeout"
DEFAULT_TRACK_HOST_TIMEOUT = 180
CONF_SENSOR_PORT_TRACKER = "sensor_port_tracker"
DEFAULT_SENSOR_PORT_TRACKER = False
CONF_SENSOR_PORT_TRAFFIC = "sensor_port_traffic"
DEFAULT_SENSOR_PORT_TRAFFIC = False
CONF_SENSOR_CLIENT_TRAFFIC = "sensor_client_traffic"
DEFAULT_SENSOR_CLIENT_TRAFFIC = False
CONF_SENSOR_CLIENT_CAPTIVE = "sensor_client_captive"
DEFAULT_SENSOR_CLIENT_CAPTIVE = False
CONF_SENSOR_SIMPLE_QUEUES = "sensor_simple_queues"
DEFAULT_SENSOR_SIMPLE_QUEUES = False
CONF_SENSOR_NAT = "sensor_nat"
DEFAULT_SENSOR_NAT = False
CONF_SENSOR_MANGLE = "sensor_mangle"
DEFAULT_SENSOR_MANGLE = False
CONF_SENSOR_FILTER = "sensor_filter"
DEFAULT_SENSOR_FILTER = False
CONF_SENSOR_PPP = "sensor_ppp"
DEFAULT_SENSOR_PPP = False
CONF_SENSOR_KIDCONTROL = "sensor_kidcontrol"
DEFAULT_SENSOR_KIDCONTROL = False
CONF_SENSOR_SCRIPTS = "sensor_scripts"
DEFAULT_SENSOR_SCRIPTS = False
CONF_SENSOR_ENVIRONMENT = "sensor_environment"
DEFAULT_SENSOR_ENVIRONMENT = False
TO_REDACT = {
"ip-address",
"client-ip-address",
"address",
"active-address",
"mac-address",
"active-mac-address",
"orig-mac-address",
"port-mac-address",
"client-mac-address",
"client-id",
"active-client-id",
"eeprom",
"sfp-vendor-serial",
"gateway",
"dns-server",
"wins-server",
"ntp-server",
"caps-manager",
"serial-number",
"source",
"from-addresses",
"to-addresses",
"src-address",
"dst-address",
"username",
"password",
"caller-id",
"target",
"ssid",
}
"""Constants used by the Mikrotik Router component and platforms."""
from homeassistant.const import Platform
PLATFORMS = [
Platform.SENSOR,
Platform.BINARY_SENSOR,
Platform.DEVICE_TRACKER,
Platform.SWITCH,
Platform.BUTTON,
Platform.UPDATE,
]
DOMAIN = "mikrotik_router"
DEFAULT_NAME = "Mikrotik Router"
ATTRIBUTION = "Data provided by Mikrotik"
DEFAULT_ENCODING = "ISO-8859-1"
DEFAULT_LOGIN_METHOD = "plain"
DEFAULT_HOST = "10.0.0.1"
DEFAULT_USERNAME = "admin"
DEFAULT_PORT = 0
DEFAULT_DEVICE_NAME = "Mikrotik"
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = False
CONF_SCAN_INTERVAL = "scan_interval"
DEFAULT_SCAN_INTERVAL = 30
CONF_TRACK_IFACE_CLIENTS = "track_iface_clients"
DEFAULT_TRACK_IFACE_CLIENTS = True
CONF_TRACK_HOSTS = "track_network_hosts"
DEFAULT_TRACK_HOSTS = False
CONF_TRACK_HOSTS_TIMEOUT = "track_network_hosts_timeout"
DEFAULT_TRACK_HOST_TIMEOUT = 180
CONF_SENSOR_PORT_TRACKER = "sensor_port_tracker"
DEFAULT_SENSOR_PORT_TRACKER = False
CONF_SENSOR_PORT_TRAFFIC = "sensor_port_traffic"
DEFAULT_SENSOR_PORT_TRAFFIC = False
CONF_SENSOR_CLIENT_TRAFFIC = "sensor_client_traffic"
DEFAULT_SENSOR_CLIENT_TRAFFIC = False
CONF_SENSOR_CLIENT_CAPTIVE = "sensor_client_captive"
DEFAULT_SENSOR_CLIENT_CAPTIVE = False
CONF_SENSOR_SIMPLE_QUEUES = "sensor_simple_queues"
DEFAULT_SENSOR_SIMPLE_QUEUES = False
CONF_SENSOR_NAT = "sensor_nat"
DEFAULT_SENSOR_NAT = False
CONF_SENSOR_MANGLE = "sensor_mangle"
DEFAULT_SENSOR_MANGLE = False
CONF_SENSOR_FILTER = "sensor_filter"
DEFAULT_SENSOR_FILTER = False
CONF_SENSOR_PPP = "sensor_ppp"
DEFAULT_SENSOR_PPP = False
CONF_SENSOR_KIDCONTROL = "sensor_kidcontrol"
DEFAULT_SENSOR_KIDCONTROL = False
CONF_SENSOR_SCRIPTS = "sensor_scripts"
DEFAULT_SENSOR_SCRIPTS = False
CONF_SENSOR_ENVIRONMENT = "sensor_environment"
DEFAULT_SENSOR_ENVIRONMENT = False
CONF_SENSOR_NETWATCH_TRACKER = "sensor_netwatch_tracker"
DEFAULT_SENSOR_NETWATCH_TRACKER = False
TO_REDACT = {
"ip-address",
"client-ip-address",
"address",
"active-address",
"mac-address",
"active-mac-address",
"orig-mac-address",
"port-mac-address",
"client-mac-address",
"client-id",
"active-client-id",
"eeprom",
"sfp-vendor-serial",
"gateway",
"dns-server",
"wins-server",
"ntp-server",
"caps-manager",
"serial-number",
"source",
"from-addresses",
"to-addresses",
"src-address",
"dst-address",
"username",
"password",
"caller-id",
"target",
"ssid",
}

View file

@ -1,42 +1,117 @@
"""Support for the Mikrotik Router device tracker."""
import logging
from typing import Any
from __future__ import annotations
from logging import getLogger
from collections.abc import Mapping
from datetime import timedelta
from typing import Any, Callable
from homeassistant.components.device_tracker.config_entry import ScannerEntity
from homeassistant.components.device_tracker.const import SOURCE_TYPE_ROUTER
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.const import STATE_NOT_HOME
from homeassistant.helpers import (
entity_platform as ep,
entity_registry as er,
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import slugify
from homeassistant.util.dt import utcnow
from homeassistant.components.device_tracker.const import SourceType
from .device_tracker_types import SENSOR_TYPES, SENSOR_SERVICES
from .coordinator import MikrotikCoordinator
from .entity import _skip_sensor, MikrotikEntity
from .helper import format_attribute
from .const import (
DOMAIN,
CONF_TRACK_HOSTS,
DEFAULT_TRACK_HOSTS,
CONF_TRACK_HOSTS_TIMEOUT,
DEFAULT_TRACK_HOST_TIMEOUT,
)
from .model import model_async_setup_entry, MikrotikEntity
from .device_tracker_types import SENSOR_TYPES, SENSOR_SERVICES
_LOGGER = logging.getLogger(__name__)
_LOGGER = getLogger(__name__)
async def async_add_entities(
hass: HomeAssistant, config_entry: ConfigEntry, dispatcher: dict[str, Callable]
):
"""Add entities."""
platform = ep.async_get_current_platform()
services = platform.platform.SENSOR_SERVICES
descriptions = platform.platform.SENSOR_TYPES
for service in services:
platform.async_register_entity_service(service[0], service[1], service[2])
@callback
async def async_update_controller(coordinator):
"""Update the values of the controller."""
if coordinator.data is None:
return
async def async_check_exist(obj, coordinator, uid: None) -> None:
"""Check entity exists."""
entity_registry = er.async_get(hass)
if uid:
unique_id = f"{obj._inst.lower()}-{obj.entity_description.key}-{slugify(str(obj._data[obj.entity_description.data_reference]).lower())}"
else:
unique_id = f"{obj._inst.lower()}-{obj.entity_description.key}"
entity_id = entity_registry.async_get_entity_id(
platform.domain, DOMAIN, unique_id
)
entity = entity_registry.async_get(entity_id)
if entity is None or (
(entity_id not in platform.entities) and (entity.disabled is False)
):
_LOGGER.debug("Add entity %s", entity_id)
await platform.async_add_entities([obj])
for entity_description in descriptions:
data = coordinator.data[entity_description.data_path]
if not entity_description.data_reference:
if data.get(entity_description.data_attribute) is None:
continue
obj = dispatcher[entity_description.func](
coordinator, entity_description
)
await async_check_exist(obj, coordinator, None)
else:
for uid in data:
if _skip_sensor(config_entry, entity_description, data, uid):
continue
obj = dispatcher[entity_description.func](
coordinator, entity_description, uid
)
await async_check_exist(obj, coordinator, uid)
await async_update_controller(
hass.data[DOMAIN][config_entry.entry_id].tracker_coordinator
)
unsub = async_dispatcher_connect(hass, "update_sensors", async_update_controller)
config_entry.async_on_unload(unsub)
# ---------------------------
# async_setup_entry
# ---------------------------
async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
_async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry for component"""
dispatcher = {
"MikrotikDeviceTracker": MikrotikDeviceTracker,
"MikrotikHostDeviceTracker": MikrotikHostDeviceTracker,
}
await model_async_setup_entry(
hass,
config_entry,
async_add_entities,
SENSOR_SERVICES,
SENSOR_TYPES,
dispatcher,
)
await async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
@ -45,6 +120,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class MikrotikDeviceTracker(MikrotikEntity, ScannerEntity):
"""Representation of a device tracker."""
def __init__(
self,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
"""Initialize entity"""
super().__init__(coordinator, entity_description, uid)
self._attr_name = None
@property
def ip_address(self) -> str:
"""Return the primary ip address of the device."""
@ -74,7 +159,7 @@ class MikrotikDeviceTracker(MikrotikEntity, ScannerEntity):
@property
def source_type(self) -> str:
"""Return the source type of the port."""
return SOURCE_TYPE_ROUTER
return SourceType.ROUTER
# ---------------------------
@ -96,11 +181,6 @@ class MikrotikHostDeviceTracker(MikrotikDeviceTracker):
)
return timedelta(seconds=track_network_hosts_timeout)
@property
def name(self) -> Any:
"""Return the name."""
return None
@property
def is_connected(self) -> bool:
"""Return true if the host is connected to the network."""
@ -136,7 +216,7 @@ class MikrotikHostDeviceTracker(MikrotikDeviceTracker):
@property
def state(self) -> str:
"""Return the state of the device."""
return self._ctrl.option_zone if self.is_connected else STATE_NOT_HOME
return self.coordinator.option_zone if self.is_connected else STATE_NOT_HOME
@property
def extra_state_attributes(self) -> Mapping[str, Any]:

View file

@ -1,6 +1,10 @@
"""Definitions for Mikrotik Router device tracker entities."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.components.switch import (
SwitchEntityDescription,
@ -12,6 +16,10 @@ DEVICE_ATTRIBUTES_HOST = [
"authorized",
"bypassed",
"last-seen",
"signal-strength",
"tx-ccq",
"tx-rate",
"rx-rate",
]
@ -19,25 +27,26 @@ DEVICE_ATTRIBUTES_HOST = [
class MikrotikDeviceTrackerEntityDescription(SwitchEntityDescription):
"""Class describing mikrotik entities."""
key: str = ""
name: str = ""
key: str | None = None
name: str | None = None
device_class = None
icon_enabled: str = ""
icon_disabled: str = ""
ha_group: str = ""
ha_connection: str = ""
ha_connection_value: str = ""
data_path: str = ""
icon_enabled: str | None = None
icon_disabled: str | None = None
ha_group: str | None = None
ha_connection: str | None = None
ha_connection_value: str | None = None
data_path: str | None = None
data_attribute: str = "available"
data_name: str = ""
data_uid: str = ""
data_reference: str = ""
data_name: str | None = None
data_name_comment: bool = False
data_uid: str | None = None
data_reference: str | None = None
data_attributes_list: List = field(default_factory=lambda: [])
func: str = "MikrotikDeviceTracker"
SENSOR_TYPES = {
"host": MikrotikDeviceTrackerEntityDescription(
SENSOR_TYPES: tuple[MikrotikDeviceTrackerEntityDescription, ...] = (
MikrotikDeviceTrackerEntityDescription(
key="host",
name="",
icon_enabled="mdi:lan-connect",
@ -52,6 +61,6 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_HOST,
func="MikrotikHostDeviceTracker",
),
}
)
SENSOR_SERVICES = {}

View file

@ -1,4 +1,5 @@
"""Diagnostics support for Mikrotik Router."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
@ -11,12 +12,14 @@ async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
controller = hass.data[DOMAIN][config_entry.entry_id]
data_coordinator = hass.data[DOMAIN][config_entry.entry_id].data_coordinator
tracker_coordinator = hass.data[DOMAIN][config_entry.entry_id].data_coordinator
return {
"entry": {
"data": async_redact_data(config_entry.data, TO_REDACT),
"options": async_redact_data(config_entry.options, TO_REDACT),
},
"data": async_redact_data(controller.data, TO_REDACT),
"data": async_redact_data(data_coordinator.data, TO_REDACT),
"tracker": async_redact_data(tracker_coordinator.data, TO_REDACT),
}

View file

@ -0,0 +1,323 @@
"""Mikrotik HA shared entity model"""
from __future__ import annotations
from collections.abc import Mapping
from logging import getLogger
from typing import Any, Callable, TypeVar
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_HOST
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
entity_platform as ep,
entity_registry as er,
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo, Entity
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import slugify
from .const import (
DOMAIN,
ATTRIBUTION,
CONF_SENSOR_PORT_TRAFFIC,
DEFAULT_SENSOR_PORT_TRAFFIC,
CONF_TRACK_HOSTS,
DEFAULT_TRACK_HOSTS,
CONF_SENSOR_PORT_TRACKER,
DEFAULT_SENSOR_PORT_TRACKER,
CONF_SENSOR_NETWATCH_TRACKER,
DEFAULT_SENSOR_NETWATCH_TRACKER,
)
from .coordinator import MikrotikCoordinator, MikrotikTrackerCoordinator
from .helper import format_attribute
_LOGGER = getLogger(__name__)
def _skip_sensor(config_entry, entity_description, data, uid) -> bool:
# Sensors
if (
entity_description.func == "MikrotikInterfaceTrafficSensor"
and not config_entry.options.get(
CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC
)
):
return True
if (
entity_description.func == "MikrotikInterfaceTrafficSensor"
and data[uid]["type"] == "bridge"
):
return True
if (
entity_description.data_path == "client_traffic"
and entity_description.data_attribute not in data[uid].keys()
):
return True
# Binary sensors
if (
entity_description.func == "MikrotikPortBinarySensor"
and data[uid]["type"] == "wlan"
):
return True
if (
entity_description.func == "MikrotikPortBinarySensor"
and not config_entry.options.get(
CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER
)
):
return True
if entity_description.data_path == "netwatch" and not config_entry.options.get(
CONF_SENSOR_NETWATCH_TRACKER, DEFAULT_SENSOR_NETWATCH_TRACKER
):
return True
# Device Tracker
if (
# Skip if host tracking is disabled
entity_description.func == "MikrotikHostDeviceTracker"
and not config_entry.options.get(CONF_TRACK_HOSTS, DEFAULT_TRACK_HOSTS)
):
return True
return False
# ---------------------------
# async_add_entities
# ---------------------------
async def async_add_entities(
hass: HomeAssistant, config_entry: ConfigEntry, dispatcher: dict[str, Callable]
):
"""Add entities."""
platform = ep.async_get_current_platform()
services = platform.platform.SENSOR_SERVICES
descriptions = platform.platform.SENSOR_TYPES
for service in services:
platform.async_register_entity_service(service[0], service[1], service[2])
@callback
async def async_update_controller(coordinator):
"""Update the values of the controller."""
async def async_check_exist(obj, coordinator, uid: None) -> None:
"""Check entity exists."""
entity_registry = er.async_get(hass)
if uid:
unique_id = f"{obj._inst.lower()}-{obj.entity_description.key}-{slugify(str(obj._data[obj.entity_description.data_reference]).lower())}"
else:
unique_id = f"{obj._inst.lower()}-{obj.entity_description.key}"
entity_id = entity_registry.async_get_entity_id(
platform.domain, DOMAIN, unique_id
)
entity = entity_registry.async_get(entity_id)
if entity is None or (
(entity_id not in platform.entities) and (entity.disabled is False)
):
_LOGGER.debug("Add entity %s", entity_id)
await platform.async_add_entities([obj])
for entity_description in descriptions:
data = coordinator.data[entity_description.data_path]
if not entity_description.data_reference:
if data.get(entity_description.data_attribute) is None:
continue
obj = dispatcher[entity_description.func](
coordinator, entity_description
)
await async_check_exist(obj, coordinator, None)
else:
for uid in data:
if _skip_sensor(config_entry, entity_description, data, uid):
continue
obj = dispatcher[entity_description.func](
coordinator, entity_description, uid
)
await async_check_exist(obj, coordinator, uid)
await async_update_controller(
hass.data[DOMAIN][config_entry.entry_id].data_coordinator
)
unsub = async_dispatcher_connect(hass, "update_sensors", async_update_controller)
config_entry.async_on_unload(unsub)
_MikrotikCoordinatorT = TypeVar(
"_MikrotikCoordinatorT",
bound=MikrotikCoordinator | MikrotikTrackerCoordinator,
)
# ---------------------------
# MikrotikEntity
# ---------------------------
class MikrotikEntity(CoordinatorEntity[_MikrotikCoordinatorT], Entity):
"""Define entity"""
_attr_has_entity_name = True
def __init__(
self,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
"""Initialize entity"""
super().__init__(coordinator)
self.entity_description = entity_description
self._inst = coordinator.config_entry.data[CONF_NAME]
self._config_entry = self.coordinator.config_entry
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._uid = uid
self._data = coordinator.data[self.entity_description.data_path]
if self._uid:
self._data = coordinator.data[self.entity_description.data_path][self._uid]
self._attr_name = self.custom_name
@callback
def _handle_coordinator_update(self) -> None:
self._data = self.coordinator.data[self.entity_description.data_path]
if self._uid:
self._data = self.coordinator.data[self.entity_description.data_path][
self._uid
]
super()._handle_coordinator_update()
@property
def custom_name(self) -> str:
"""Return the name for this entity"""
if not self._uid:
if self.entity_description.data_name_comment and self._data["comment"]:
return f"{self._data['comment']}"
return f"{self.entity_description.name}"
if self.entity_description.data_name_comment and self._data["comment"]:
return f"{self._data['comment']}"
if self.entity_description.name:
if (
self._data[self.entity_description.data_reference]
== self._data[self.entity_description.data_name]
):
return f"{self.entity_description.name}"
return f"{self._data[self.entity_description.data_name]} {self.entity_description.name}"
return f"{self._data[self.entity_description.data_name]}"
@property
def unique_id(self) -> str:
"""Return a unique id for this entity"""
if self._uid:
return f"{self._inst.lower()}-{self.entity_description.key}-{slugify(str(self._data[self.entity_description.data_reference]).lower())}"
else:
return f"{self._inst.lower()}-{self.entity_description.key}"
# @property
# def available(self) -> bool:
# """Return if controller is available"""
# return self.coordinator.connected()
@property
def device_info(self) -> DeviceInfo:
"""Return a description for device registry."""
dev_connection = DOMAIN
dev_connection_value = self.entity_description.data_reference
dev_group = self.entity_description.ha_group
if self.entity_description.ha_group == "System":
dev_group = self.coordinator.data["resource"]["board-name"]
dev_connection_value = self.coordinator.data["routerboard"]["serial-number"]
if self.entity_description.ha_group.startswith("data__"):
dev_group = self.entity_description.ha_group[6:]
if dev_group in self._data:
dev_group = self._data[dev_group]
dev_connection_value = dev_group
if self.entity_description.ha_connection:
dev_connection = self.entity_description.ha_connection
if self.entity_description.ha_connection_value:
dev_connection_value = self.entity_description.ha_connection_value
if dev_connection_value.startswith("data__"):
dev_connection_value = dev_connection_value[6:]
dev_connection_value = self._data[dev_connection_value]
if self.entity_description.ha_group == "System":
return DeviceInfo(
connections={(dev_connection, f"{dev_connection_value}")},
identifiers={(dev_connection, f"{dev_connection_value}")},
name=f"{self._inst} {dev_group}",
model=f"{self.coordinator.data['resource']['board-name']}",
manufacturer=f"{self.coordinator.data['resource']['platform']}",
sw_version=f"{self.coordinator.data['resource']['version']}",
configuration_url=f"http://{self.coordinator.config_entry.data[CONF_HOST]}",
)
elif "mac-address" in self.entity_description.data_reference:
dev_group = self._data[self.entity_description.data_name]
dev_manufacturer = ""
if dev_connection_value in self.coordinator.data["host"]:
dev_group = self.coordinator.data["host"][dev_connection_value][
"host-name"
]
dev_manufacturer = self.coordinator.data["host"][dev_connection_value][
"manufacturer"
]
return DeviceInfo(
connections={(dev_connection, f"{dev_connection_value}")},
default_name=f"{dev_group}",
default_manufacturer=f"{dev_manufacturer}",
via_device=(
DOMAIN,
f"{self.coordinator.data['routerboard']['serial-number']}",
),
)
else:
return DeviceInfo(
connections={(dev_connection, f"{dev_connection_value}")},
default_name=f"{self._inst} {dev_group}",
default_model=f"{self.coordinator.data['resource']['board-name']}",
default_manufacturer=f"{self.coordinator.data['resource']['platform']}",
via_device=(
DOMAIN,
f"{self.coordinator.data['routerboard']['serial-number']}",
),
)
@property
def extra_state_attributes(self) -> Mapping[str, Any]:
"""Return the state attributes."""
attributes = super().extra_state_attributes
for variable in self.entity_description.data_attributes_list:
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
return attributes
async def start(self):
"""Dummy run function"""
raise NotImplementedError()
async def stop(self):
"""Dummy stop function"""
raise NotImplementedError()
async def restart(self):
"""Dummy restart function"""
raise NotImplementedError()
async def reload(self):
"""Dummy reload function"""
raise NotImplementedError()

View file

@ -7,7 +7,7 @@
"issue_tracker": "https://github.com/tomaae/homeassistant-mikrotik_router/issues",
"dependencies": [],
"requirements": [
"librouteros>=3.2.0",
"librouteros>=3.4.1",
"mac-vendor-lookup>=0.1.12"
],
"codeowners": [

View file

@ -28,12 +28,14 @@ class MikrotikAPI:
password,
port=0,
use_ssl=True,
ssl_verify=True,
login_method=DEFAULT_LOGIN_METHOD,
encoding=DEFAULT_ENCODING,
):
"""Initialize the Mikrotik Client."""
self._host = host
self._use_ssl = use_ssl
self._ssl_verify = ssl_verify
self._port = port
self._username = username
self._password = password
@ -44,12 +46,13 @@ class MikrotikAPI:
self._connection = None
self._connected = False
self._reconnected = False
self._reconnected = True
self._connection_epoch = 0
self._connection_retry_sec = 58
self.error = None
self.connection_error_reported = False
self.client_traffic_last_run = None
self.disable_health = False
# Default ports
if not self._port:
@ -118,15 +121,19 @@ class MikrotikAPI:
"port": self._port,
}
if self._use_ssl:
if self._ssl_wrapper is None:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
self._ssl_wrapper = ssl_context.wrap_socket
kwargs["ssl_wrapper"] = self._ssl_wrapper
self.lock.acquire()
try:
if self._use_ssl:
if self._ssl_wrapper is None:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
if self._ssl_verify:
ssl_context.verify_mode = ssl.CERT_REQUIRED
ssl_context.verify_flags &= ~ssl.VERIFY_X509_STRICT
else:
ssl_context.verify_mode = ssl.CERT_NONE
self._ssl_wrapper = ssl_context.wrap_socket
kwargs["ssl_wrapper"] = self._ssl_wrapper
self._connection = librouteros.connect(
self._host, self._username, self._password, **kwargs
)
@ -164,6 +171,9 @@ class MikrotikAPI:
if "ALERT_HANDSHAKE_FAILURE" in error:
self.error = "ssl_handshake_failure"
if "CERTIFICATE_VERIFY_FAILED" in error:
self.error = "ssl_verify_failure"
# ---------------------------
# connected
# ---------------------------
@ -177,6 +187,9 @@ class MikrotikAPI:
def query(self, path, command=None, args=None, return_list=True) -> Optional(list):
"""Retrieve data from Mikrotik API."""
"""Returns generator object, unless return_list passed as True"""
if path == "/system/health" and self.disable_health:
return None
if args is None:
args = {}
@ -196,9 +209,15 @@ class MikrotikAPI:
try:
response = list(response)
except Exception as e:
self.disconnect("building list for path", e)
if path == "/system/health" and "no such command prefix" in str(e):
self.disable_health = True
self.lock.release()
return None
self.disconnect(f"building list for path {path}", e)
self.lock.release()
return None
elif response and command:
_LOGGER.debug("API query: %s, %s, %s", path, command, args)
try:
@ -258,7 +277,7 @@ class MikrotikAPI:
# ---------------------------
# execute
# ---------------------------
def execute(self, path, command, param, value) -> bool:
def execute(self, path, command, param, value, attributes=None) -> bool:
"""Execute a command"""
entry_found = None
params = {}
@ -292,6 +311,9 @@ class MikrotikAPI:
params = {".id": entry_found}
if attributes:
params.update(attributes)
self.lock.acquire()
try:
tuple(response(command, **params))

View file

@ -1,316 +0,0 @@
"""Mikrotik HA shared entity model"""
from logging import getLogger
from typing import Any
from collections.abc import Mapping
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import callback
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_HOST
from .helper import format_attribute
from .const import (
DOMAIN,
ATTRIBUTION,
CONF_SENSOR_PORT_TRAFFIC,
DEFAULT_SENSOR_PORT_TRAFFIC,
CONF_TRACK_HOSTS,
DEFAULT_TRACK_HOSTS,
CONF_SENSOR_PORT_TRACKER,
DEFAULT_SENSOR_PORT_TRACKER,
)
_LOGGER = getLogger(__name__)
def _skip_sensor(config_entry, uid_sensor, uid_data, uid) -> bool:
# Sensors
if (
uid_sensor.func == "MikrotikInterfaceTrafficSensor"
and not config_entry.options.get(
CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC
)
):
return True
if (
uid_sensor.func == "MikrotikInterfaceTrafficSensor"
and uid_data[uid]["type"] == "bridge"
):
return True
if (
uid_sensor.func == "MikrotikClientTrafficSensor"
and uid_sensor.data_attribute not in uid_data[uid].keys()
):
return True
# Binary sensors
if (
uid_sensor.func == "MikrotikPortBinarySensor"
and uid_data[uid]["type"] == "wlan"
):
return True
if uid_sensor.func == "MikrotikPortBinarySensor" and not config_entry.options.get(
CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER
):
return True
# Device Tracker
if (
# Skip if host tracking is disabled
uid_sensor.func == "MikrotikHostDeviceTracker"
and not config_entry.options.get(CONF_TRACK_HOSTS, DEFAULT_TRACK_HOSTS)
):
return True
return False
# ---------------------------
# model_async_setup_entry
# ---------------------------
async def model_async_setup_entry(
hass, config_entry, async_add_entities, sensor_services, sensor_types, dispatcher
):
inst = config_entry.data[CONF_NAME]
mikrotik_controller = hass.data[DOMAIN][config_entry.entry_id]
sensors = {}
platform = entity_platform.async_get_current_platform()
for service in sensor_services:
platform.async_register_entity_service(service[0], service[1], service[2])
@callback
def update_controller():
"""Update the values of the controller"""
model_update_items(
inst,
config_entry,
mikrotik_controller,
async_add_entities,
sensors,
dispatcher,
sensor_types,
)
mikrotik_controller.listeners.append(
async_dispatcher_connect(
hass, mikrotik_controller.signal_update, update_controller
)
)
update_controller()
# ---------------------------
# model_update_items
# ---------------------------
def model_update_items(
inst,
config_entry,
mikrotik_controller,
async_add_entities,
sensors,
dispatcher,
sensor_types,
):
def _register_entity(_sensors, _item_id, _uid, _uid_sensor):
_LOGGER.debug("Updating entity %s (%s)", inst, _item_id)
if _item_id in _sensors:
return None
return dispatcher[_uid_sensor.func](
inst=inst,
uid=_uid,
mikrotik_controller=mikrotik_controller,
entity_description=_uid_sensor,
)
new_sensors = []
for sensor in sensor_types:
uid_sensor = sensor_types[sensor]
if not uid_sensor.data_reference:
if (
uid_sensor.data_attribute
not in mikrotik_controller.data[uid_sensor.data_path]
or mikrotik_controller.data[uid_sensor.data_path][
uid_sensor.data_attribute
]
== "unknown"
):
continue
item_id = f"{inst}-{sensor}"
if tmp := _register_entity(sensors, item_id, "", uid_sensor):
sensors[item_id] = tmp
new_sensors.append(sensors[item_id])
else:
for uid in mikrotik_controller.data[uid_sensor.data_path]:
uid_data = mikrotik_controller.data[uid_sensor.data_path]
if _skip_sensor(config_entry, uid_sensor, uid_data, uid):
continue
item_id = f"{inst}-{sensor}-{str(uid_data[uid][uid_sensor.data_reference]).lower()}"
if tmp := _register_entity(sensors, item_id, uid, uid_sensor):
sensors[item_id] = tmp
new_sensors.append(sensors[item_id])
if new_sensors:
async_add_entities(new_sensors, True)
# ---------------------------
# MikrotikEntity
# ---------------------------
class MikrotikEntity:
"""Define entity"""
_attr_has_entity_name = True
def __init__(
self,
inst,
uid: "",
mikrotik_controller,
entity_description,
):
"""Initialize entity"""
self.entity_description = entity_description
self._inst = inst
self._ctrl = mikrotik_controller
self._config_entry = self._ctrl.config_entry
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._uid = uid
if self._uid:
self._data = mikrotik_controller.data[self.entity_description.data_path][
self._uid
]
else:
self._data = mikrotik_controller.data[self.entity_description.data_path]
@property
def name(self) -> str:
"""Return the name for this entity"""
if not self._uid:
if self.entity_description.data_name_comment and self._data["comment"]:
return f"{self._data['comment']}"
return f"{self.entity_description.name}"
if self.entity_description.data_name_comment and self._data["comment"]:
return f"{self._data['comment']}"
if self.entity_description.name:
if (
self._data[self.entity_description.data_reference]
== self._data[self.entity_description.data_name]
):
return f"{self.entity_description.name}"
return f"{self._data[self.entity_description.data_name]} {self.entity_description.name}"
return f"{self._data[self.entity_description.data_name]}"
@property
def unique_id(self) -> str:
"""Return a unique id for this entity"""
if self._uid:
return f"{self._inst.lower()}-{self.entity_description.key}-{str(self._data[self.entity_description.data_reference]).lower()}"
else:
return f"{self._inst.lower()}-{self.entity_description.key}"
@property
def available(self) -> bool:
"""Return if controller is available"""
return self._ctrl.connected()
@property
def device_info(self) -> DeviceInfo:
"""Return a description for device registry."""
dev_connection = DOMAIN
dev_connection_value = self.entity_description.data_reference
dev_group = self.entity_description.ha_group
if self.entity_description.ha_group == "System":
dev_group = self._ctrl.data["resource"]["board-name"]
dev_connection_value = self._ctrl.data["routerboard"]["serial-number"]
if self.entity_description.ha_group.startswith("data__"):
dev_group = self.entity_description.ha_group[6:]
if dev_group in self._data:
dev_group = self._data[dev_group]
dev_connection_value = dev_group
if self.entity_description.ha_connection:
dev_connection = self.entity_description.ha_connection
if self.entity_description.ha_connection_value:
dev_connection_value = self.entity_description.ha_connection_value
if dev_connection_value.startswith("data__"):
dev_connection_value = dev_connection_value[6:]
dev_connection_value = self._data[dev_connection_value]
info = DeviceInfo(
connections={(dev_connection, f"{dev_connection_value}")},
identifiers={(dev_connection, f"{dev_connection_value}")},
default_name=f"{self._inst} {dev_group}",
default_model=f"{self._ctrl.data['resource']['board-name']}",
default_manufacturer=f"{self._ctrl.data['resource']['platform']}",
sw_version=f"{self._ctrl.data['resource']['version']}",
configuration_url=f"http://{self._ctrl.config_entry.data[CONF_HOST]}",
via_device=(DOMAIN, f"{self._ctrl.data['routerboard']['serial-number']}"),
)
if "mac-address" in self.entity_description.data_reference:
dev_group = self._data[self.entity_description.data_name]
dev_manufacturer = ""
if dev_connection_value in self._ctrl.data["host"]:
dev_group = self._ctrl.data["host"][dev_connection_value]["host-name"]
dev_manufacturer = self._ctrl.data["host"][dev_connection_value][
"manufacturer"
]
info = DeviceInfo(
connections={(dev_connection, f"{dev_connection_value}")},
default_name=f"{dev_group}",
default_manufacturer=f"{dev_manufacturer}",
via_device=(
DOMAIN,
f"{self._ctrl.data['routerboard']['serial-number']}",
),
)
return info
@property
def extra_state_attributes(self) -> Mapping[str, Any]:
"""Return the state attributes."""
attributes = super().extra_state_attributes
for variable in self.entity_description.data_attributes_list:
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
return attributes
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass"""
_LOGGER.debug("New entity %s (%s)", self._inst, self.unique_id)
async def async_will_remove_from_hass(self) -> None:
"""Run when entity about to be removed from hass"""
_LOGGER.debug("Removing entity %s (%s)", self._inst, self.unique_id)
async def start(self):
"""Dummy run function"""
_LOGGER.error("Start functionality does not exist for %s", self.unique_id)
async def stop(self):
"""Dummy stop function"""
_LOGGER.error("Stop functionality does not exist for %s", self.unique_id)
async def restart(self):
"""Dummy restart function"""
_LOGGER.error("Restart functionality does not exist for %s", self.unique_id)
async def reload(self):
"""Dummy reload function"""
_LOGGER.error("Reload functionality does not exist for %s", self.unique_id)

View file

@ -1,11 +1,22 @@
"""Implementation of Mikrotik Router sensor entities."""
"""Mikrotik sensor platform."""
import logging
from typing import Any, Optional
from __future__ import annotations
from logging import getLogger
from collections.abc import Mapping
from datetime import date, datetime
from decimal import Decimal
from typing import Any
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import MikrotikCoordinator
from .entity import MikrotikEntity, async_add_entities
from .helper import format_attribute
from .model import model_async_setup_entry, MikrotikEntity
from .sensor_types import (
SENSOR_TYPES,
SENSOR_SERVICES,
@ -14,52 +25,56 @@ from .sensor_types import (
DEVICE_ATTRIBUTES_IFACE_WIRELESS,
)
_LOGGER = logging.getLogger(__name__)
_LOGGER = getLogger(__name__)
# ---------------------------
# async_setup_entry
# ---------------------------
async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
_async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry for component"""
dispatcher = {
"MikrotikSensor": MikrotikSensor,
"MikrotikInterfaceTrafficSensor": MikrotikInterfaceTrafficSensor,
"MikrotikClientTrafficSensor": MikrotikClientTrafficSensor,
}
await model_async_setup_entry(
hass,
config_entry,
async_add_entities,
SENSOR_SERVICES,
SENSOR_TYPES,
dispatcher,
)
await async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
# MikrotikSensor
# ---------------------------
class MikrotikSensor(MikrotikEntity, SensorEntity):
"""Define an Mikrotik Controller sensor."""
"""Define an Mikrotik sensor."""
def __init__(
self,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
super().__init__(coordinator, entity_description, uid)
self._attr_suggested_unit_of_measurement = (
self.entity_description.suggested_unit_of_measurement
)
@property
def state(self) -> Optional[str]:
"""Return the state."""
if self.entity_description.data_attribute:
return self._data[self.entity_description.data_attribute]
else:
return "unknown"
def native_value(self) -> StateType | date | datetime | Decimal:
"""Return the value reported by the sensor."""
return self._data[self.entity_description.data_attribute]
@property
def native_unit_of_measurement(self):
def native_unit_of_measurement(self) -> str | None:
"""Return the unit the value is expressed in."""
if self.entity_description.native_unit_of_measurement:
if self.entity_description.native_unit_of_measurement.startswith("data__"):
uom = self.entity_description.native_unit_of_measurement[6:]
if uom in self._data:
uom = self._data[uom]
return uom
return self._data[uom]
return self.entity_description.native_unit_of_measurement
@ -102,20 +117,20 @@ class MikrotikClientTrafficSensor(MikrotikSensor):
"""Define an Mikrotik MikrotikClientTrafficSensor sensor."""
@property
def name(self) -> str:
"""Return the name."""
def custom_name(self) -> str:
"""Return the name for this entity"""
return f"{self.entity_description.name}"
@property
def available(self) -> bool:
"""Return if controller and accounting feature in Mikrotik is available.
Additional check for lan-tx/rx sensors
"""
if self.entity_description.data_attribute in ["lan-tx", "lan-rx"]:
return (
self._ctrl.connected()
and self._data["available"]
and self._data["local_accounting"]
)
else:
return self._ctrl.connected() and self._data["available"]
# @property
# def available(self) -> bool:
# """Return if controller and accounting feature in Mikrotik is available.
# Additional check for lan-tx/rx sensors
# """
# if self.entity_description.data_attribute in ["lan-tx", "lan-rx"]:
# return (
# self.coordinator.connected()
# and self._data["available"]
# and self._data["local_accounting"]
# )
# else:
# return self.coordinator.connected() and self._data["available"]

View file

@ -1,6 +1,10 @@
"""Definitions for Mikrotik Router sensor entities."""
"""Definitions for sensor entities."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.entity import EntityCategory
from homeassistant.components.sensor import (
@ -9,12 +13,16 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
)
from homeassistant.const import (
TEMP_CELSIUS,
ELECTRIC_POTENTIAL_VOLT,
POWER_WATT,
PERCENTAGE,
DATA_BYTES,
REVOLUTIONS_PER_MINUTE,
UnitOfTemperature,
UnitOfDataRate,
UnitOfInformation,
UnitOfElectricPotential,
UnitOfElectricCurrent,
UnitOfPower,
)
from .const import DOMAIN
DEVICE_ATTRIBUTES_IFACE = [
@ -113,28 +121,30 @@ DEVICE_ATTRIBUTES_GPS = [
class MikrotikSensorEntityDescription(SensorEntityDescription):
"""Class describing mikrotik entities."""
ha_group: str = ""
ha_connection: str = ""
ha_connection_value: str = ""
data_path: str = ""
data_attribute: str = ""
data_name: str = ""
ha_group: str | None = None
ha_connection: str | None = None
ha_connection_value: str | None = None
data_path: str | None = None
data_attribute: str | None = None
data_name: str | None = None
data_name_comment: bool = False
data_uid: str = ""
data_reference: str = ""
data_uid: str | None = None
data_reference: str | None = None
data_attributes_list: List = field(default_factory=lambda: [])
func: str = "MikrotikSensor"
SENSOR_TYPES = {
"system_temperature": MikrotikSensorEntityDescription(
SENSOR_TYPES: tuple[MikrotikSensorEntityDescription, ...] = (
MikrotikSensorEntityDescription(
key="system_temperature",
name="Temperature",
icon="mdi:thermometer",
native_unit_of_measurement=TEMP_CELSIUS,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=0,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="temperature",
@ -142,11 +152,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_voltage": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_voltage",
name="Voltage",
icon="mdi:lightning-bolt",
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
suggested_unit_of_measurement=UnitOfElectricPotential.VOLT,
suggested_display_precision=1,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
@ -157,14 +169,16 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_cpu-temperature": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_cpu-temperature",
name="CPU temperature",
icon="mdi:thermometer",
native_unit_of_measurement=TEMP_CELSIUS,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=0,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="cpu-temperature",
@ -172,14 +186,33 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_board-temperature1": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_switch-temperature",
name="Switch temperature",
icon="mdi:thermometer",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=0,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="switch-temperature",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_board-temperature1",
name="Board temperature",
icon="mdi:thermometer",
native_unit_of_measurement=TEMP_CELSIUS,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=0,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="board-temperature1",
@ -187,14 +220,33 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_power-consumption": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_phy-temperature",
name="PHY temperature",
icon="mdi:thermometer",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=0,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="phy-temperature",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_power-consumption",
name="Power consumption",
icon="mdi:transmission-tower",
native_unit_of_measurement=POWER_WATT,
native_unit_of_measurement=UnitOfPower.WATT,
suggested_unit_of_measurement=UnitOfPower.WATT,
suggested_display_precision=0,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="power-consumption",
@ -202,14 +254,31 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_fan1-speed": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_poe_out_consumption",
name="PoE out power consumption",
icon="mdi:flash",
native_unit_of_measurement=UnitOfPower.WATT,
suggested_unit_of_measurement=UnitOfPower.WATT,
suggested_display_precision=1,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="poe-out-consumption",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_fan1-speed",
name="Fan1 speed",
icon="mdi:fan",
native_unit_of_measurement="RPM",
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
device_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="fan1-speed",
@ -217,14 +286,14 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_fan2-speed": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_fan2-speed",
name="Fan2 speed",
icon="mdi:fan",
native_unit_of_measurement="RPM",
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
device_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="fan2-speed",
@ -232,7 +301,122 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_uptime": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_fan3-speed",
name="Fan3 speed",
icon="mdi:fan",
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
device_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="fan3-speed",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_fan4-speed",
name="Fan4 speed",
icon="mdi:fan",
native_unit_of_measurement=REVOLUTIONS_PER_MINUTE,
device_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="fan4-speed",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_poe_out_consumption",
name="PoE out power consumption",
icon="mdi:transmission-tower",
native_unit_of_measurement=UnitOfPower.WATT,
suggested_unit_of_measurement=UnitOfPower.WATT,
suggested_display_precision=1,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="poe-out-consumption",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_psu1_current",
name="PSU 1 power consumption",
icon="mdi:lightning-bolt-circle",
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
suggested_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
suggested_display_precision=1,
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="psu1-current",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_psu1_voltage",
name="PSU 1 Voltage",
icon="mdi:lightning-bolt",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
suggested_unit_of_measurement=UnitOfElectricPotential.VOLT,
suggested_display_precision=1,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="psu1-voltage",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_psu2_current",
name="PSU 2 power consumption",
icon="mdi:lightning-bolt-circle",
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
suggested_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
suggested_display_precision=1,
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="psu2-current",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_psu2_voltage",
name="PSU 2 Voltage",
icon="mdi:lightning-bolt",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
suggested_unit_of_measurement=UnitOfElectricPotential.VOLT,
suggested_display_precision=1,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="psu2-voltage",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_uptime",
name="Uptime",
icon=None,
@ -247,7 +431,7 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_cpu-load": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_cpu-load",
name="CPU load",
icon="mdi:speedometer",
@ -262,7 +446,7 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_memory-usage": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_memory-usage",
name="Memory usage",
icon="mdi:memory",
@ -277,7 +461,7 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_hdd-usage": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_hdd-usage",
name="HDD usage",
icon="mdi:harddisk",
@ -292,13 +476,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_clients-wired": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_clients-wired",
name="Wired clients",
icon="mdi:lan",
native_unit_of_measurement=None,
device_class=None,
state_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="System",
data_path="resource",
@ -307,13 +491,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_clients-wireless": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_clients-wireless",
name="Wireless clients",
icon="mdi:wifi",
native_unit_of_measurement=None,
device_class=None,
state_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="System",
data_path="resource",
@ -322,13 +506,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_captive-authorized": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_captive-authorized",
name="Captive portal clients",
icon="mdi:key-wireless",
native_unit_of_measurement=None,
device_class=None,
state_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="System",
data_path="resource",
@ -337,7 +521,7 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_gps-latitude": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_gps-latitude",
name="Latitude",
icon="mdi:latitude",
@ -353,7 +537,7 @@ SENSOR_TYPES = {
data_reference="",
data_attributes_list=DEVICE_ATTRIBUTES_GPS,
),
"system_gps-longitude": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_gps-longitude",
name="Longitude",
icon="mdi:longitude",
@ -369,12 +553,14 @@ SENSOR_TYPES = {
data_reference="",
data_attributes_list=DEVICE_ATTRIBUTES_GPS,
),
"traffic_tx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="traffic_tx",
name="TX",
icon="mdi:upload-network-outline",
native_unit_of_measurement="data__tx-attr",
device_class=None,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="data__default-name",
@ -388,12 +574,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikInterfaceTrafficSensor",
),
"traffic_rx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="traffic_rx",
name="RX",
icon="mdi:download-network-outline",
native_unit_of_measurement="data__rx-attr",
device_class=None,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="data__default-name",
@ -407,12 +595,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikInterfaceTrafficSensor",
),
"total_tx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="tx-total",
name="TX total",
icon="mdi:upload-network",
native_unit_of_measurement=DATA_BYTES,
device_class=None,
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIGABYTES,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_SIZE,
state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=None,
ha_group="data__default-name",
@ -426,12 +616,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikInterfaceTrafficSensor",
),
"total_rx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="rx-total",
name="RX total",
icon="mdi:download-network",
native_unit_of_measurement=DATA_BYTES,
device_class=None,
native_unit_of_measurement=UnitOfInformation.BYTES,
suggested_unit_of_measurement=UnitOfInformation.GIGABYTES,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_SIZE,
state_class=SensorStateClass.TOTAL_INCREASING,
entity_category=None,
ha_group="data__default-name",
@ -445,12 +637,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikInterfaceTrafficSensor",
),
"client_traffic_lan_tx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="client_traffic_lan_tx",
name="LAN TX",
icon="mdi:upload-network",
native_unit_of_measurement="data__tx-rx-attr",
device_class=None,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="",
@ -464,12 +658,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC,
func="MikrotikClientTrafficSensor",
),
"client_traffic_lan_rx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="client_traffic_lan_rx",
name="LAN RX",
icon="mdi:download-network",
native_unit_of_measurement="data__tx-rx-attr",
device_class=None,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="",
@ -483,12 +679,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC,
func="MikrotikClientTrafficSensor",
),
"client_traffic_wan_tx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="client_traffic_wan_tx",
name="WAN TX",
icon="mdi:upload-network",
native_unit_of_measurement="data__tx-rx-attr",
device_class=None,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="",
@ -502,12 +700,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC,
func="MikrotikClientTrafficSensor",
),
"client_traffic_wan_rx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="client_traffic_wan_rx",
name="WAN RX",
icon="mdi:download-network",
native_unit_of_measurement="data__tx-rx-attr",
device_class=None,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="",
@ -521,12 +721,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC,
func="MikrotikClientTrafficSensor",
),
"client_traffic_tx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="client_traffic_tx",
name="TX",
icon="mdi:upload-network",
native_unit_of_measurement="data__tx-rx-attr",
device_class=None,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="",
@ -540,12 +742,14 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC,
func="MikrotikClientTrafficSensor",
),
"client_traffic_rx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="client_traffic_rx",
name="RX",
icon="mdi:download-network",
native_unit_of_measurement="data__tx-rx-attr",
device_class=None,
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
suggested_unit_of_measurement=UnitOfDataRate.KILOBYTES_PER_SECOND,
suggested_display_precision=1,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="",
@ -559,7 +763,7 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC,
func="MikrotikClientTrafficSensor",
),
"environment": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="environment",
name="",
icon="mdi:clipboard-list",
@ -576,6 +780,6 @@ SENSOR_TYPES = {
data_uid="name",
data_reference="name",
),
}
)
SENSOR_SERVICES = {}
SENSOR_SERVICES = []

View file

@ -1,9 +1 @@
run_script:
description: Run script on Mikrotik
fields:
router:
description: Name of the router
example: "Mikrotik"
script:
description: Name of the script
example: "MyScript"
---

View file

@ -10,7 +10,8 @@
"port": "Port",
"username": "Username",
"password": "Password",
"ssl": "Use SSL"
"ssl": "Use SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -18,6 +19,7 @@
"name_exists": "Name already exists.",
"cannot_connect": "Cannot connect to Mikrotik.",
"ssl_handshake_failure": "SSL handshake failure",
"ssl_verify_failure": "Certificate verify failed",
"connection_timeout": "Mikrotik connection timeout.",
"wrong_login": "Invalid user name or password."
}
@ -39,6 +41,7 @@
"data": {
"track_network_hosts": "Track network devices",
"sensor_port_tracker": "Port tracker sensors",
"sensor_netwatch_tracker": "Netwatch tracker sensors",
"sensor_port_traffic": "Port traffic sensors",
"sensor_client_traffic": "Client traffic sensors",
"sensor_client_captive": "Captive portal data",

View file

@ -1,12 +1,19 @@
"""Support for the Mikrotik Router switches."""
import logging
from typing import Any, Optional
from __future__ import annotations
from logging import getLogger
from collections.abc import Mapping
from typing import Any, Optional
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from .entity import MikrotikEntity, async_add_entities
from .helper import format_attribute
from .model import model_async_setup_entry, MikrotikEntity
from .switch_types import (
SENSOR_TYPES,
SENSOR_SERVICES,
@ -15,13 +22,17 @@ from .switch_types import (
DEVICE_ATTRIBUTES_IFACE_WIRELESS,
)
_LOGGER = logging.getLogger(__name__)
_LOGGER = getLogger(__name__)
# ---------------------------
# async_setup_entry
# ---------------------------
async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
_async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry for component"""
dispatcher = {
"MikrotikSwitch": MikrotikSwitch,
@ -32,14 +43,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
"MikrotikQueueSwitch": MikrotikQueueSwitch,
"MikrotikKidcontrolPauseSwitch": MikrotikKidcontrolPauseSwitch,
}
await model_async_setup_entry(
hass,
config_entry,
async_add_entities,
SENSOR_SERVICES,
SENSOR_TYPES,
dispatcher,
)
await async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
@ -71,27 +75,27 @@ class MikrotikSwitch(MikrotikEntity, SwitchEntity, RestoreEntity):
async def async_turn_on(self) -> None:
"""Turn on the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = self.entity_description.data_reference
value = self._data[self.entity_description.data_reference]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, False)
await self._ctrl.force_update()
self.coordinator.set_value(path, param, value, mod_param, False)
await self.coordinator.async_refresh()
async def async_turn_off(self) -> None:
"""Turn off the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = self.entity_description.data_reference
value = self._data[self.entity_description.data_reference]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, True)
await self._ctrl.async_update()
self.coordinator.set_value(path, param, value, mod_param, True)
await self.coordinator.async_refresh()
# ---------------------------
@ -137,7 +141,7 @@ class MikrotikPortSwitch(MikrotikSwitch):
async def async_turn_on(self) -> Optional[str]:
"""Turn on the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
@ -149,17 +153,17 @@ class MikrotikPortSwitch(MikrotikSwitch):
param = "name"
value = self._data[self.entity_description.data_reference]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, False)
self.coordinator.set_value(path, param, value, mod_param, False)
if "poe-out" in self._data and self._data["poe-out"] == "off":
path = "/interface/ethernet"
self._ctrl.set_value(path, param, value, "poe-out", "auto-on")
self.coordinator.set_value(path, param, value, "poe-out", "auto-on")
await self._ctrl.force_update()
await self.coordinator.async_refresh()
async def async_turn_off(self) -> Optional[str]:
"""Turn off the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
@ -171,13 +175,13 @@ class MikrotikPortSwitch(MikrotikSwitch):
param = "name"
value = self._data[self.entity_description.data_reference]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, True)
self.coordinator.set_value(path, param, value, mod_param, True)
if "poe-out" in self._data and self._data["poe-out"] == "auto-on":
path = "/interface/ethernet"
self._ctrl.set_value(path, param, value, "poe-out", "off")
self.coordinator.set_value(path, param, value, "poe-out", "off")
await self._ctrl.async_update()
await self.coordinator.async_refresh()
# ---------------------------
@ -188,43 +192,43 @@ class MikrotikNATSwitch(MikrotikSwitch):
async def async_turn_on(self) -> None:
"""Turn on the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = ".id"
value = None
for uid in self._ctrl.data["nat"]:
if self._ctrl.data["nat"][uid]["uniq-id"] == (
for uid in self.coordinator.data["nat"]:
if self.coordinator.data["nat"][uid]["uniq-id"] == (
f"{self._data['chain']},{self._data['action']},{self._data['protocol']},"
f"{self._data['in-interface']}:{self._data['dst-port']}-"
f"{self._data['out-interface']}:{self._data['to-addresses']}:{self._data['to-ports']}"
):
value = self._ctrl.data["nat"][uid][".id"]
value = self.coordinator.data["nat"][uid][".id"]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, False)
await self._ctrl.force_update()
self.coordinator.set_value(path, param, value, mod_param, False)
await self.coordinator.async_refresh()
async def async_turn_off(self) -> None:
"""Turn off the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = ".id"
value = None
for uid in self._ctrl.data["nat"]:
if self._ctrl.data["nat"][uid]["uniq-id"] == (
for uid in self.coordinator.data["nat"]:
if self.coordinator.data["nat"][uid]["uniq-id"] == (
f"{self._data['chain']},{self._data['action']},{self._data['protocol']},"
f"{self._data['in-interface']}:{self._data['dst-port']}-"
f"{self._data['out-interface']}:{self._data['to-addresses']}:{self._data['to-ports']}"
):
value = self._ctrl.data["nat"][uid][".id"]
value = self.coordinator.data["nat"][uid][".id"]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, True)
await self._ctrl.async_update()
self.coordinator.set_value(path, param, value, mod_param, True)
await self.coordinator.async_refresh()
# ---------------------------
@ -235,45 +239,45 @@ class MikrotikMangleSwitch(MikrotikSwitch):
async def async_turn_on(self) -> None:
"""Turn on the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = ".id"
value = None
for uid in self._ctrl.data["mangle"]:
if self._ctrl.data["mangle"][uid]["uniq-id"] == (
for uid in self.coordinator.data["mangle"]:
if self.coordinator.data["mangle"][uid]["uniq-id"] == (
f"{self._data['chain']},{self._data['action']},{self._data['protocol']},"
f"{self._data['src-address']}:{self._data['src-port']}-"
f"{self._data['dst-address']}:{self._data['dst-port']},"
f"{self._data['src-address-list']}-{self._data['dst-address-list']}"
):
value = self._ctrl.data["mangle"][uid][".id"]
value = self.coordinator.data["mangle"][uid][".id"]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, False)
await self._ctrl.force_update()
self.coordinator.set_value(path, param, value, mod_param, False)
await self.coordinator.async_refresh()
async def async_turn_off(self) -> None:
"""Turn off the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = ".id"
value = None
for uid in self._ctrl.data["mangle"]:
if self._ctrl.data["mangle"][uid]["uniq-id"] == (
for uid in self.coordinator.data["mangle"]:
if self.coordinator.data["mangle"][uid]["uniq-id"] == (
f"{self._data['chain']},{self._data['action']},{self._data['protocol']},"
f"{self._data['src-address']}:{self._data['src-port']}-"
f"{self._data['dst-address']}:{self._data['dst-port']},"
f"{self._data['src-address-list']}-{self._data['dst-address-list']}"
):
value = self._ctrl.data["mangle"][uid][".id"]
value = self.coordinator.data["mangle"][uid][".id"]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, True)
await self._ctrl.async_update()
self.coordinator.set_value(path, param, value, mod_param, True)
await self.coordinator.async_refresh()
# ---------------------------
@ -284,43 +288,43 @@ class MikrotikFilterSwitch(MikrotikSwitch):
async def async_turn_on(self) -> None:
"""Turn on the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = ".id"
value = None
for uid in self._ctrl.data["filter"]:
if self._ctrl.data["filter"][uid]["uniq-id"] == (
for uid in self.coordinator.data["filter"]:
if self.coordinator.data["filter"][uid]["uniq-id"] == (
f"{self._data['chain']},{self._data['action']},{self._data['protocol']},{self._data['layer7-protocol']},"
f"{self._data['in-interface']},{self._data['in-interface-list']}:{self._data['src-address']},{self._data['src-address-list']}:{self._data['src-port']}-"
f"{self._data['out-interface']},{self._data['out-interface-list']}:{self._data['dst-address']},{self._data['dst-address-list']}:{self._data['dst-port']}"
):
value = self._ctrl.data["filter"][uid][".id"]
value = self.coordinator.data["filter"][uid][".id"]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, False)
await self._ctrl.force_update()
self.coordinator.set_value(path, param, value, mod_param, False)
await self.coordinator.async_refresh()
async def async_turn_off(self) -> None:
"""Turn off the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = ".id"
value = None
for uid in self._ctrl.data["filter"]:
if self._ctrl.data["filter"][uid]["uniq-id"] == (
for uid in self.coordinator.data["filter"]:
if self.coordinator.data["filter"][uid]["uniq-id"] == (
f"{self._data['chain']},{self._data['action']},{self._data['protocol']},{self._data['layer7-protocol']},"
f"{self._data['in-interface']},{self._data['in-interface-list']}:{self._data['src-address']},{self._data['src-address-list']}:{self._data['src-port']}-"
f"{self._data['out-interface']},{self._data['out-interface-list']}:{self._data['dst-address']},{self._data['dst-address-list']}:{self._data['dst-port']}"
):
value = self._ctrl.data["filter"][uid][".id"]
value = self.coordinator.data["filter"][uid][".id"]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, True)
await self._ctrl.async_update()
self.coordinator.set_value(path, param, value, mod_param, True)
await self.coordinator.async_refresh()
# ---------------------------
@ -331,35 +335,35 @@ class MikrotikQueueSwitch(MikrotikSwitch):
async def async_turn_on(self) -> None:
"""Turn on the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = ".id"
value = None
for uid in self._ctrl.data["queue"]:
if self._ctrl.data["queue"][uid]["name"] == f"{self._data['name']}":
value = self._ctrl.data["queue"][uid][".id"]
for uid in self.coordinator.data["queue"]:
if self.coordinator.data["queue"][uid]["name"] == f"{self._data['name']}":
value = self.coordinator.data["queue"][uid][".id"]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, False)
await self._ctrl.force_update()
self.coordinator.set_value(path, param, value, mod_param, False)
await self.coordinator.async_refresh()
async def async_turn_off(self) -> None:
"""Turn off the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = ".id"
value = None
for uid in self._ctrl.data["queue"]:
if self._ctrl.data["queue"][uid]["name"] == f"{self._data['name']}":
value = self._ctrl.data["queue"][uid][".id"]
for uid in self.coordinator.data["queue"]:
if self.coordinator.data["queue"][uid]["name"] == f"{self._data['name']}":
value = self.coordinator.data["queue"][uid][".id"]
mod_param = self.entity_description.data_switch_parameter
self._ctrl.set_value(path, param, value, mod_param, True)
await self._ctrl.async_update()
self.coordinator.set_value(path, param, value, mod_param, True)
await self.coordinator.async_refresh()
# ---------------------------
@ -370,24 +374,24 @@ class MikrotikKidcontrolPauseSwitch(MikrotikSwitch):
async def async_turn_on(self) -> None:
"""Turn on the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = self.entity_description.data_reference
value = self._data[self.entity_description.data_reference]
command = "resume"
self._ctrl.execute(path, command, param, value)
await self._ctrl.force_update()
self.coordinator.execute(path, command, param, value)
await self.coordinator.async_refresh()
async def async_turn_off(self) -> None:
"""Turn off the switch."""
if "write" not in self._ctrl.data["access"]:
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = self.entity_description.data_reference
value = self._data[self.entity_description.data_reference]
command = "pause"
self._ctrl.execute(path, command, param, value)
await self._ctrl.async_update()
self.coordinator.execute(path, command, param, value)
await self.coordinator.async_refresh()

View file

@ -1,4 +1,7 @@
"""Definitions for Mikrotik Router switch entities."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
@ -127,6 +130,7 @@ DEVICE_ATTRIBUTES_PPP_SECRET = [
]
DEVICE_ATTRIBUTES_KIDCONTROL = [
"blocked",
"rate-limit",
"mon",
"tue",
@ -163,25 +167,25 @@ class MikrotikSwitchEntityDescription(SwitchEntityDescription):
device_class: str = SwitchDeviceClass.SWITCH
icon_enabled: str = ""
icon_disabled: str = ""
ha_group: str = ""
ha_connection: str = ""
ha_connection_value: str = ""
data_path: str = ""
icon_enabled: str | None = None
icon_disabled: str | None = None
ha_group: str | None = None
ha_connection: str | None = None
ha_connection_value: str | None = None
data_path: str | None = None
data_attribute: str = "enabled"
data_switch_path: str = ""
data_switch_path: str | None = None
data_switch_parameter: str = "disabled"
data_name: str = ""
data_name: str | None = None
data_name_comment: bool = False
data_uid: str = ""
data_reference: str = ""
data_uid: str | None = None
data_reference: str | None = None
data_attributes_list: List = field(default_factory=lambda: [])
func: str = "MikrotikSwitch"
SENSOR_TYPES = {
"interface": MikrotikSwitchEntityDescription(
SENSOR_TYPES: tuple[MikrotikSwitchEntityDescription, ...] = (
MikrotikSwitchEntityDescription(
key="interface",
name="Port",
icon_enabled="mdi:lan-connect",
@ -198,7 +202,7 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikPortSwitch",
),
"nat": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="nat",
name="",
icon_enabled="mdi:network-outline",
@ -216,7 +220,7 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_NAT,
func="MikrotikNATSwitch",
),
"mangle": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="mangle",
name="",
icon_enabled="mdi:bookmark-outline",
@ -234,7 +238,7 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_MANGLE,
func="MikrotikMangleSwitch",
),
"filter": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="filter",
name="",
icon_enabled="mdi:filter-variant",
@ -252,7 +256,7 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_FILTER,
func="MikrotikFilterSwitch",
),
"ppp_secret": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="ppp_secret",
name="PPP Secret",
icon_enabled="mdi:account-outline",
@ -268,7 +272,7 @@ SENSOR_TYPES = {
data_reference="name",
data_attributes_list=DEVICE_ATTRIBUTES_PPP_SECRET,
),
"queue": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="queue",
name="",
icon_enabled="mdi:leaf",
@ -285,7 +289,7 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_QUEUE,
func="MikrotikQueueSwitch",
),
"kidcontrol_enable": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="kidcontrol_enable",
name="",
icon_enabled="mdi:account",
@ -301,7 +305,7 @@ SENSOR_TYPES = {
data_reference="name",
data_attributes_list=DEVICE_ATTRIBUTES_KIDCONTROL,
),
"kidcontrol_pause": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="kidcontrol_paused",
name="paused",
icon_enabled="mdi:account-outline",
@ -319,6 +323,6 @@ SENSOR_TYPES = {
data_attributes_list=DEVICE_ATTRIBUTES_KIDCONTROL,
func="MikrotikKidcontrolPauseSwitch",
),
}
)
SENSOR_SERVICES = {}

View file

@ -10,7 +10,8 @@
"port": "منفذ",
"username": "اسم المستخدم",
"password": "كلمة المرور",
"ssl": "استعمل طبقة الوصلات الآمنة (SSL)"
"ssl": "استعمل طبقة الوصلات الآمنة (SSL)",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "لا يمكن الاتصال بـ (Mikrotik).",
"ssl_handshake_failure": "فشل تأكيد الاتصال (SSL)",
"connection_timeout": "مهلة اتصال (Mikrotik).",
"wrong_login": "خطأ في اسم المستخدم أو كلمة مرور."
"wrong_login": "خطأ في اسم المستخدم أو كلمة مرور.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "مفاتيح المِكواة الأُسطوانية",
"sensor_ppp": "مستخدمي PPP",
"sensor_filter": "مفاتيح الترشيح",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "خيارات جهاز Mikrotik Router (2\/2)",
"description": "تمكين أجهزة الاستشعار والمفاتيح"

View file

@ -10,7 +10,8 @@
"port": "Port",
"username": "Uživatel",
"password": "Heslo",
"ssl": "Použití SSL"
"ssl": "Použití SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Nelze se připojit k Mikrotiku.",
"ssl_handshake_failure": "Selhání handshake SSL",
"connection_timeout": "Časový limit připojení vypršel.",
"wrong_login": "Neplatné uživatelské jméno nebo heslo."
"wrong_login": "Neplatné uživatelské jméno nebo heslo.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Mangle spínače",
"sensor_ppp": "Uživatelé PPP",
"sensor_filter": "Přepínače filtrů",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portál data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Nastavení Mikrotik Router (2\/2)",
"description": "Povolit senzory a přepínače"

View file

@ -10,16 +10,18 @@
"port": "Port",
"username": "Benutzername",
"password": "Passwort",
"ssl": "SSL benutzen"
"ssl": "SSL benutzen",
"verify_ssl": "Verify SSL"
}
}
},
"error": {
"name_exists": "Name existiert bereits",
"cannot_connect": "Verbindung zu Mikrotik nicht möglich.",
"ssl_handshake_failure": "SSL Vereinbarung-Fehler",
"cannot_connect": "Verbindung zum MikroTik fehlgeschlagen.",
"ssl_handshake_failure": "SSL Handshake Fehler",
"connection_timeout": "Mikrotik-Verbindungstimeout.",
"wrong_login": "Ungültiger Benutzername oder Passwort."
"wrong_login": "Ungültiger Benutzername oder Passwort.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -37,19 +39,20 @@
},
"sensor_select": {
"data": {
"track_network_hosts": "Verfolgen Sie Netzwerkgeräte",
"sensor_port_tracker": "Hafentracker-Sensoren",
"sensor_port_traffic": "Hafen-Verkehrssensoren",
"sensor_client_traffic": "Kunden-Verkehrssensoren",
"track_network_hosts": "Netzwerkgeräte verfolgen",
"sensor_port_tracker": "Port-Statussensoren",
"sensor_port_traffic": "Port-Durchsatz Sensoren",
"sensor_client_traffic": "Client-Durchsatz Sensoren",
"sensor_simple_queues": "Einfache Endschalter",
"sensor_nat": "NAT-Schalter",
"sensor_scripts": "Skript-Schalter",
"sensor_environment": "Umwelt variable Sensoren",
"sensor_kidcontrol": "Kinderkontrolle",
"sensor_environment": "Umgebungsvariablen-Sensoren",
"sensor_kidcontrol": "Kindersicherung",
"sensor_mangle": "Mangle-Schalter",
"sensor_ppp": "PPP-Nutzer",
"sensor_filter": "Schalter filtern",
"sensor_client_captive": "Captive portal data"
"sensor_filter": "Schalter für Filterregeln",
"sensor_client_captive": "Captive-Portal Daten",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik-Router-Optionen (2\/2)",
"description": "Sensoren und Schalter aktivieren"

View file

@ -10,7 +10,8 @@
"port": "Θύρα",
"username": "Όνομα χρήστη",
"password": "Κωδικός πρόσβασης",
"ssl": "Χρήση SSL"
"ssl": "Χρήση SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Αδυναμία σύνδεσης στον Mikrotik.",
"ssl_handshake_failure": "Αποτυχίας χειραψίας SSL",
"connection_timeout": "Διακοπή σύνδεσης με Mikrotik.",
"wrong_login": "Το όνομα χρήστη και ο κωδικός πρόσβασης δεν είναι έγκυρα."
"wrong_login": "Το όνομα χρήστη και ο κωδικός πρόσβασης δεν είναι έγκυρα.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Επιλογικοί διακόπτες",
"sensor_ppp": "Χρήστες PPP",
"sensor_filter": "Διακόπτες φίλτρου",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Επιλογές Mikrotik Router (2\/2)",
"description": "Ενεργοποίηση αισθητήρων και διακοπτών"

View file

@ -10,7 +10,8 @@
"port": "Port",
"username": "Username",
"password": "Password",
"ssl": "Use SSL"
"ssl": "Use SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Cannot connect to Mikrotik.",
"ssl_handshake_failure": "SSL handshake failure",
"connection_timeout": "Mikrotik connection timeout.",
"wrong_login": "Invalid user name or password."
"wrong_login": "Invalid user name or password.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router options (2\/2)",
"description": "Enable sensors and switches"

View file

@ -10,7 +10,8 @@
"port": "Puerto",
"username": "Nombre de usuario",
"password": "Contraseña",
"ssl": "Usar SSL"
"ssl": "Usar SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "No es posible conectar con el Mikrotik.",
"ssl_handshake_failure": "Fallo en el establecimiento de la comunicación SSL",
"connection_timeout": "Conexión con Mikrotik sin respuesta.",
"wrong_login": "Nombre de usuario o contraseña no válidos."
"wrong_login": "Nombre de usuario o contraseña no válidos.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Interruptor Mangle",
"sensor_ppp": "Usuarios PPP",
"sensor_filter": "Filtros de interruptores",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Opciones del Mikrotik Router 2\/2)",
"description": "Activar sensores e interruptores"

View file

@ -10,7 +10,8 @@
"port": "Puerto",
"username": "Nombre de usuario",
"password": "Contraseña",
"ssl": "Utilizar SSL"
"ssl": "Utilizar SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "No puede conectarse a Mikrotik.",
"ssl_handshake_failure": "Fallo del \"handshake\" SSL",
"connection_timeout": "Tiempo de espera de la conexión a Mikrotik.",
"wrong_login": "Nombre de usuario o contraseña no válida"
"wrong_login": "Nombre de usuario o contraseña no válida",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Conmutadores de mangle",
"sensor_ppp": "Usuarios PPP",
"sensor_filter": "Conmutadores de filtro",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Datos portal cautivo",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Opciones del Mikrotik Router (2\/2)",
"description": "Activar sensores e interruptores"

View file

@ -7,10 +7,11 @@
"data": {
"name": "Nom de l'intégration",
"host": "Hôte",
"port": "Port",
"port": "Interface",
"username": "Identifiant",
"password": "Mot de passe",
"ssl": "Utiliser SSL"
"ssl": "Utiliser SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Impossible de se connecter à Mikrotik.",
"ssl_handshake_failure": "Échec de la négociation SSL",
"connection_timeout": "Expiration de connexion Mikrotik.",
"wrong_login": "Identifiant ou mot de passe invalide."
"wrong_login": "Identifiant ou mot de passe invalide.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -38,21 +40,22 @@
"sensor_select": {
"data": {
"track_network_hosts": "Suivre les appareils du réseau",
"sensor_port_tracker": "Capteurs de suivi de ports",
"sensor_port_traffic": "Capteurs de trafic par ports",
"sensor_port_tracker": "Capteurs d'état des interfaces",
"sensor_port_traffic": "Capteurs de trafic par interface",
"sensor_client_traffic": "Capteurs de trafic de clients",
"sensor_simple_queues": "Commutateurs de files d'attente simples",
"sensor_nat": "Commutateurs NAT",
"sensor_nat": "Règles NAT",
"sensor_scripts": "Commutation de scripts",
"sensor_environment": "Capteurs de variables d'environnement",
"sensor_kidcontrol": "Contrôle parental",
"sensor_mangle": "Commutateurs Mangle",
"sensor_mangle": "Règles de Mangle",
"sensor_ppp": "Utilisateurs PPP",
"sensor_filter": "Sélecteurs de filtre",
"sensor_client_captive": "Captive portal data"
"sensor_filter": "Règles de filtrage",
"sensor_client_captive": "Données du portail captif",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Options Mikrotik Router (2\/2)",
"description": "Activer les capteurs et les interrupteurs"
"description": "Activer les capteurs et les sélecteurs"
}
}
}

View file

@ -10,7 +10,8 @@
"port": "पोर्ट",
"username": "उपयोगकर्ता नाम",
"password": "पासवर्ड",
"ssl": "SSL का उपयोग करें"
"ssl": "SSL का उपयोग करें",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Mikrotik से कनेक्ट नही कर सकते।",
"ssl_handshake_failure": "SSL हैन्डशेक विफ़ल हो गया",
"connection_timeout": "Mikrotik कनेक्शन टाइम आउट हो गया।",
"wrong_login": "अमान्य उपयोगकर्ता नाम या पासवर्ड।"
"wrong_login": "अमान्य उपयोगकर्ता नाम या पासवर्ड।",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "मैंगल स्विच",
"sensor_ppp": "PPP यूज़र",
"sensor_filter": "फ़िल्टर स्विच",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router के विकल्प (2\/2)",
"description": "सेंसर और स्विच को एनेबल करें"

View file

@ -10,7 +10,8 @@
"port": "Port",
"username": "Felhasználónév",
"password": "Jelszó",
"ssl": "SSL használata"
"ssl": "SSL használata",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Nem lehet csatlakozni a Mikrotikhez.",
"ssl_handshake_failure": "SSL-kézfogás sikertelen",
"connection_timeout": "Mikrotik kapcsolat időtúllépés.",
"wrong_login": "Érvénytelen felhasználónév vagy jelszó."
"wrong_login": "Érvénytelen felhasználónév vagy jelszó.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Mangle kapcsolók",
"sensor_ppp": "PPP felhasználók",
"sensor_filter": "Szűrő kapcsolók",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router opciók (2\/2)",
"description": "Érzékelők és kapcsolók engedélyezése"

View file

@ -10,7 +10,8 @@
"port": "Port",
"username": "Notandanafn",
"password": "Lykilorð",
"ssl": "Nota SSL"
"ssl": "Nota SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Get ekki tengst Mikrotik.",
"ssl_handshake_failure": "Villa við að koma á SSL samskiptum",
"connection_timeout": "Tengin við Mikrotik rann út á tíma.",
"wrong_login": "Ógilt notendanafn eða lykilorð."
"wrong_login": "Ógilt notendanafn eða lykilorð.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Bjögunarrofi",
"sensor_ppp": "PPP notendur",
"sensor_filter": "Síurofar",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router valkostir (2\/2)",
"description": "Virkja skynjara og rofa"

View file

@ -10,7 +10,8 @@
"port": "Porta",
"username": "Nome utente",
"password": "Password",
"ssl": "Usa SSL"
"ssl": "Usa SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Impossibile connettersi a Mikrotik.",
"ssl_handshake_failure": "Errore SSL handshake",
"connection_timeout": "Timeout connessione Mikrotik.",
"wrong_login": "Nome utente o password non validi."
"wrong_login": "Nome utente o password non validi.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Interruttori a manganello",
"sensor_ppp": "Utenti PPP",
"sensor_filter": "Interruttori del filtro",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Opzioni Mikrotik Router (2\/2)",
"description": "Abilita sensori e interruttori"

View file

@ -10,7 +10,8 @@
"port": "ポート",
"username": "ユーザ名",
"password": "パスワード",
"ssl": "SSLを使用します"
"ssl": "SSLを使用します",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Mikrotikに接続できません。",
"ssl_handshake_failure": "SSLハンドシェイクに失敗しました",
"connection_timeout": "Mikrotik接続がタイムアウトしました。",
"wrong_login": "ユーザ名またはパスワードが無効です。"
"wrong_login": "ユーザ名またはパスワードが無効です。",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "マングルスイッチ",
"sensor_ppp": "PPPユーザー",
"sensor_filter": "フィルタースイッチ",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router オプション (2\/2)",
"description": "センサーとスイッチを有効にする"

View file

@ -10,7 +10,8 @@
"port": "포트",
"username": "사용자 이름",
"password": "비밀번호",
"ssl": "SSL 사용"
"ssl": "SSL 사용",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Mikrotik에 연결할 수 없습니다.",
"ssl_handshake_failure": "SSL 핸드셰이크 실패",
"connection_timeout": "Mikrotik 연결 시간이 초과되었습니다.",
"wrong_login": "사용자 이름 또는 비밀번호가 유효하지 않습니다."
"wrong_login": "사용자 이름 또는 비밀번호가 유효하지 않습니다.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "맹글 스위치",
"sensor_ppp": "PPP 사용자",
"sensor_filter": "필터 스위치",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router 옵션(2\/2)",
"description": "센서 및 스위치 활성화"

View file

@ -10,7 +10,8 @@
"port": "Ports",
"username": "Lietotājvārds",
"password": "Parole",
"ssl": "Izmantot SSL"
"ssl": "Izmantot SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Neizdodas savienoties ar Mikrotik.",
"ssl_handshake_failure": "SSL savienojuma kļūda",
"connection_timeout": "Mikrotik savienojuma noilgums.",
"wrong_login": "Nederīgs lietotājvārds vai parole."
"wrong_login": "Nederīgs lietotājvārds vai parole.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Manglu slēdži",
"sensor_ppp": "PPP lietotāji",
"sensor_filter": "Filtra slēdži",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router opcijas (2\/2)",
"description": "Sensoru un slēdžu aktivizēšana"

View file

@ -10,7 +10,8 @@
"port": "Poort",
"username": "Gebruikersnaam",
"password": "Wachtwoord",
"ssl": "Gebruik SSL"
"ssl": "Gebruik SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Kan geen verbinding maken met Mikrotik.",
"ssl_handshake_failure": "SSL overeenkomings-storing",
"connection_timeout": "Mikrotik verbinding time-out.",
"wrong_login": "Ongeldige gebruikersnaam of wachtwoord."
"wrong_login": "Ongeldige gebruikersnaam of wachtwoord.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Mangelschakelaars",
"sensor_ppp": "PPP-gebruikers",
"sensor_filter": "Filterschakelaars",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Opties Mikrotik Router (2\/2)",
"description": "Sensoren en schakelaars inschakelen"

View file

@ -0,0 +1,62 @@
{
"config": {
"step": {
"user": {
"title": "Sett opp Mikrotik router",
"description": "Sett opp Mikrotik Router integrasjon",
"data": {
"name": "Navn på integreringen",
"host": "Adresse",
"port": "Port",
"username": "Brukernavn",
"password": "Passord",
"ssl": "Bruk SSL",
"verify_ssl": "Verify SSL"
}
}
},
"error": {
"name_exists": "Navnet eksisterer allerede",
"cannot_connect": "Kunne ikke koble til Mikrotik",
"ssl_handshake_failure": "SSH håndtrykkfeil",
"connection_timeout": "Mikrotik tilkoblingen fikk tidsavbrudd",
"wrong_login": "Feil brukernavn og\/eller passord",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
"step": {
"basic_options": {
"data": {
"scan_interval": "Søke interval (krever HA omstart)",
"track_iface_clients": "Vis klient MAC og IP på grensesnittet",
"unit_of_measurement": "Måleenhet",
"track_network_hosts_timeout": "Spor nettverks enhet tidsavbrudd (sekunder)",
"zone": "Sone for enhetssporer"
},
"title": "Mikrotik Router alternativer",
"description": "Konfigurer integrasjon"
},
"sensor_select": {
"data": {
"track_network_hosts": "Spor nettverksenheter",
"sensor_port_tracker": "Port sporings sensorer",
"sensor_port_traffic": "Port trafikk sensorer",
"sensor_client_traffic": "Klient trafikk sensorer",
"sensor_simple_queues": "Enklel kø brytere",
"sensor_nat": "NAT Brytere",
"sensor_scripts": "Script brytere",
"sensor_environment": "Miljøvariabel sensorer",
"sensor_kidcontrol": "Barnekontroll",
"sensor_mangle": "Mangle brytere",
"sensor_ppp": "PPP Brukere",
"sensor_filter": "Filter brytere",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router options (2\/2)",
"description": "Skru på sensorer og brytere"
}
}
}
}

View file

@ -10,7 +10,8 @@
"port": "Port",
"username": "Nazwa użytkownika",
"password": "Hasło",
"ssl": "Użyj SSL"
"ssl": "Użyj SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Nie można połączyć się z Mikrotik.",
"ssl_handshake_failure": "Błąd uzgadniania SSL",
"connection_timeout": "Limit czasu dla połączenia Mikrotik został przekroczony.",
"wrong_login": "Nieprawidłowa nazwa użytkownika lub hasło."
"wrong_login": "Nieprawidłowa nazwa użytkownika lub hasło.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Przełączniki mangle",
"sensor_ppp": "Użytkownicy PPP",
"sensor_filter": "Przełączniki filtrów",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Opcje Mikrotik Router (2\/2)",
"description": "Włącz czujniki i przełączniki"

View file

@ -10,7 +10,8 @@
"port": "Porta",
"username": "Nome de utilizador",
"password": "Palavra-passe",
"ssl": "Usar SSL"
"ssl": "Usar SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Não é possível ligar à Mikrotik.",
"ssl_handshake_failure": "Falha no reconhecimento SSL",
"connection_timeout": "Tempo limite da ligação à Mikrotik.",
"wrong_login": "Nome de utilizador ou palavra-passe inválidos."
"wrong_login": "Nome de utilizador ou palavra-passe inválidos.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Interruptores mangle",
"sensor_ppp": "Utilizadores PPP",
"sensor_filter": "Interruptores de filtro",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Opções do Mikrotik Router (2\/2)",
"description": "Ativar sensores e interruptores"

View file

@ -10,7 +10,8 @@
"port": "Porta",
"username": "Nome de usuário",
"password": "Senha",
"ssl": "Usar SSL"
"ssl": "Usar SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Não consigo conectar ao Mikrotik.",
"ssl_handshake_failure": "Falha no handshake SSL",
"connection_timeout": "Tempo limite de conexão do Mikrotik.",
"wrong_login": "Nome de usuário ou senha inválidos."
"wrong_login": "Nome de usuário ou senha inválidos.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Interruptores Mangle",
"sensor_ppp": "Usuários de PPP",
"sensor_filter": "Interruptores de filtro",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "sensores de cliente cativo",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Opções de roteador Mikrotik (2\/2)",
"description": "Ativar sensores e interruptores"

View file

@ -6,11 +6,12 @@
"description": "Настройка Home Assistant для интеграции с Mikrotik.",
"data": {
"name": "Название интеграции",
"host": "Адрес маршрутизатора",
"host": "Адрес хоста",
"port": "Порт",
"username": "Имя пользователя",
"password": "Пароль",
"ssl": "Использовать SSL"
"ssl": "Использовать SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Не удалось подключиться.",
"ssl_handshake_failure": "Ошибка SSL-соединения.",
"connection_timeout": "Истекло время подключения к Mikrotik.",
"wrong_login": "Неверное имя пользователя или пароль."
"wrong_login": "Неверное имя пользователя или пароль.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Переключатели Mangle",
"sensor_ppp": "PPP-пользователи",
"sensor_filter": "Переключатели фильтров",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Данные портала авторизации",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Параметры Mikrotik Router (2\/2)",
"description": "Включить датчики и переключатели"

View file

@ -10,7 +10,8 @@
"port": "Port",
"username": "Užívateľ",
"password": "Heslo",
"ssl": "Použiť SSL"
"ssl": "Použiť SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Nedá sa pripojiť k Mikrotiku.",
"ssl_handshake_failure": "Zlyhanie nadviazania spojenia SSL.",
"connection_timeout": "Časový limit pripojenia vypršal.",
"wrong_login": "Nesprávne užívateľské meno alebo heslo."
"wrong_login": "Nesprávne užívateľské meno alebo heslo.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Mangle prepínače",
"sensor_ppp": "PPP používatelia",
"sensor_filter": "Prepínače filtrov",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Možnosti Mikrotik Router (2\/2)",
"description": "Povoliť senzory a spínače"

View file

@ -10,7 +10,8 @@
"port": "Port",
"username": "Kullanıcı adı",
"password": "Şifre",
"ssl": "SSL kullan"
"ssl": "SSL kullan",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Mikrotik'e bağlanılamıyor.",
"ssl_handshake_failure": "SSL uyuşma hatası",
"connection_timeout": "Mikrotik bağlantısı zaman aşımı.",
"wrong_login": "Geçersiz kullanıcı adı veya şifresi."
"wrong_login": "Geçersiz kullanıcı adı veya şifresi.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Mangle anahtarları",
"sensor_ppp": "PPP kullanıcıları",
"sensor_filter": "Filtre anahtarları",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router seçenekleri (2\/2)",
"description": "Sensör ve anahtarları etkinleştir"

View file

@ -0,0 +1,62 @@
{
"config": {
"step": {
"user": {
"title": "Налаштувати Mikrotik Router",
"description": "Налаштувати Mikrotik Router інтеграцію",
"data": {
"name": "Назва інтеграції",
"host": "Хост",
"port": "Порт",
"username": "Користувач",
"password": "Пароль",
"ssl": "Використовувати SSL",
"verify_ssl": "Verify SSL"
}
}
},
"error": {
"name_exists": "Назва вже існує.",
"cannot_connect": "Не можу з'єднатися з Mikrotik.",
"ssl_handshake_failure": "Помилка SSH протоколу",
"connection_timeout": "Тайм-аут з'єднання з Mikrotik",
"wrong_login": "Невірне ім'я або пароль",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
"step": {
"basic_options": {
"data": {
"scan_interval": "Інтервал сканування (вимагає перезавантаження HA)",
"track_iface_clients": "Показувати MAC і IP адреси клієнта",
"unit_of_measurement": "Одиниці вимірювання",
"track_network_hosts_timeout": "Тайм-аут відстеження мережевих пристроїв (секунди)",
"zone": "Зона для трекера пристроїв"
},
"title": "Налаштування Mikrotik роутера (1\/2)",
"description": "Налаштувати інтеграцію"
},
"sensor_select": {
"data": {
"track_network_hosts": "Відстежувати мережеві пристрої",
"sensor_port_tracker": "Сенсори трекерів портів",
"sensor_port_traffic": "Сенсори трафіку портів",
"sensor_client_traffic": "Сенсори трафіку клієнтів",
"sensor_simple_queues": "Прості черги і перемикачі",
"sensor_nat": "Перемикачі NAT",
"sensor_scripts": "Перемикачі скриптів",
"sensor_environment": "Сенсори змінних оточення",
"sensor_kidcontrol": "Дитячий контроль",
"sensor_mangle": "Керувати перемикачами",
"sensor_ppp": "Користувачі PPP",
"sensor_filter": "Фільтрувати перемикачі",
"sensor_client_captive": "Дані Captive portal",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router options (2\/2)",
"description": "Увімкнути сенсори і перемикачі"
}
}
}
}

View file

@ -10,7 +10,8 @@
"port": "Cổng",
"username": "Tên người dùng",
"password": "Mật khẩu",
"ssl": "Sử dụng SSL"
"ssl": "Sử dụng SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "Không thể kết nối với Mikrotik.",
"ssl_handshake_failure": "Lỗi kết nối SSL",
"connection_timeout": "Hết thời gian chờ kết nối Mikrotik.",
"wrong_login": "Tên người dùng hoặc mật khẩu không hợp lệ."
"wrong_login": "Tên người dùng hoặc mật khẩu không hợp lệ.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "Công tắc Mangle",
"sensor_ppp": "Người dùng PPP",
"sensor_filter": "Công tắc bộ lọc",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Cài đặt Mikrotik Router (2\/2)",
"description": "Bật cảm biến và công tắc"

View file

@ -10,7 +10,8 @@
"port": "端口",
"username": "用户名",
"password": "密码",
"ssl": "使用 SSL"
"ssl": "使用 SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -19,7 +20,8 @@
"cannot_connect": "无法连接到 Mikrotik。",
"ssl_handshake_failure": "SSL 交握失败",
"connection_timeout": "Mikrotik 连接超时。",
"wrong_login": "无效的用户名或密码。"
"wrong_login": "无效的用户名或密码。",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -49,7 +51,8 @@
"sensor_mangle": "标记开关",
"sensor_ppp": "PPP 用户",
"sensor_filter": "过滤器开关",
"sensor_client_captive": "Captive portal data"
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Mikrotik Router 选项 (2\/2)",
"description": "启动传感器和开关"

View file

@ -1,40 +1,48 @@
"""Support for the Mikrotik Router update service."""
import logging
from __future__ import annotations
import asyncio
from logging import getLogger
from typing import Any
from requests import get as requests_get
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.components.update import (
UpdateEntity,
UpdateDeviceClass,
UpdateEntityFeature,
)
from .model import model_async_setup_entry, MikrotikEntity
from .coordinator import MikrotikCoordinator
from .entity import MikrotikEntity, async_add_entities
from .update_types import (
SENSOR_TYPES,
SENSOR_SERVICES,
)
from packaging.version import Version
_LOGGER = logging.getLogger(__name__)
_LOGGER = getLogger(__name__)
DEVICE_UPDATE = "device_update"
# ---------------------------
# async_setup_entry
# ---------------------------
async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
_async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry for component"""
dispatcher = {
"MikrotikRouterOSUpdate": MikrotikRouterOSUpdate,
"MikrotikRouterBoardFWUpdate": MikrotikRouterBoardFWUpdate,
}
await model_async_setup_entry(
hass,
config_entry,
async_add_entities,
SENSOR_SERVICES,
SENSOR_TYPES,
dispatcher,
)
await async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
@ -45,13 +53,12 @@ class MikrotikRouterOSUpdate(MikrotikEntity, UpdateEntity):
def __init__(
self,
inst,
uid: "",
mikrotik_controller,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
"""Set up device update entity."""
super().__init__(inst, uid, mikrotik_controller, entity_description)
super().__init__(coordinator, entity_description, uid)
self._attr_supported_features = UpdateEntityFeature.INSTALL
self._attr_supported_features |= UpdateEntityFeature.BACKUP
@ -79,25 +86,30 @@ class MikrotikRouterOSUpdate(MikrotikEntity, UpdateEntity):
async def async_install(self, version: str, backup: bool, **kwargs: Any) -> None:
"""Install an update."""
if backup:
self._ctrl.execute("/system/backup", "save", None, None)
self.coordinator.execute("/system/backup", "save", None, None)
self._ctrl.execute("/system/package/update", "install", None, None)
self.coordinator.execute("/system/package/update", "install", None, None)
async def async_release_notes(self) -> str:
"""Return the release notes."""
try:
response = await self._ctrl.hass.async_add_executor_job(
requests_get,
f"https://mikrotik.com/download/changelogs?ax=loadLog&val={self._data['latest-version']}",
session = async_get_clientsession(self.hass)
"""Get concatenated changelogs from installed_version to latest_version in reverse order."""
versions_to_fetch = generate_version_list(
self._data["installed-version"], self._data["latest-version"]
)
if response.status_code == 200:
return response.text.replace(chr(10), "<br />")
tasks = [fetch_changelog(session, version) for version in versions_to_fetch]
changelogs = await asyncio.gather(*tasks)
# Combine all non-empty changelogs, maintaining reverse order
combined_changelogs = "\n\n".join(filter(None, changelogs))
return combined_changelogs.replace("*) ", "- ")
except Exception as e:
_LOGGER.warning("Failed to download release notes (%s)", e)
return "Failed to download release notes"
return "Error fetching release notes."
@property
def release_url(self) -> str:
@ -116,13 +128,12 @@ class MikrotikRouterBoardFWUpdate(MikrotikEntity, UpdateEntity):
def __init__(
self,
inst,
uid: "",
mikrotik_controller,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
"""Set up device update entity."""
super().__init__(inst, uid, mikrotik_controller, entity_description)
super().__init__(coordinator, entity_description, uid)
self._attr_supported_features = UpdateEntityFeature.INSTALL
self._attr_title = self.entity_description.title
@ -150,5 +161,49 @@ class MikrotikRouterBoardFWUpdate(MikrotikEntity, UpdateEntity):
async def async_install(self, version: str, backup: bool, **kwargs: Any) -> None:
"""Install an update."""
self._ctrl.execute("/system/routerboard", "upgrade", None, None)
self._ctrl.execute("/system", "reboot", None, None)
self.coordinator.execute("/system/routerboard", "upgrade", None, None)
self.coordinator.execute("/system", "reboot", None, None)
async def fetch_changelog(session, version: str) -> str:
"""Asynchronously fetch the changelog for a given version."""
url = f"https://cdn.mikrotik.com/routeros/{version}/CHANGELOG"
try:
async with session.get(url) as response:
if response.status == 200:
text = await response.text()
return text.replace("*) ", "- ")
except Exception as e:
pass
return ""
def generate_version_list(start_version: str, end_version: str) -> list:
"""Generate a list of version strings from start_version to end_version in reverse order."""
start = Version(start_version)
end = Version(end_version)
versions = []
current = end
while current >= start:
versions.append(str(current))
current = decrement_version(current, start)
return versions
def decrement_version(version: Version, start_version: Version) -> Version:
"""Decrement version by the smallest possible step without going below start_version."""
if version.micro > 0:
next_patch = version.micro - 1
return Version(f"{version.major}.{version.minor}.{next_patch}")
elif version.minor > 0:
next_minor = version.minor - 1
return Version(
f"{version.major}.{next_minor}.999"
) # Assuming .999 as max patch version
else:
next_major = version.major - 1
return Version(
f"{next_major}.999.999"
) # Assuming .999 as max minor and patch version

View file

@ -1,6 +1,10 @@
"""Definitions for Mikrotik Router update entities."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
from homeassistant.components.update import UpdateEntityDescription
@ -8,22 +12,22 @@ from homeassistant.components.update import UpdateEntityDescription
class MikrotikUpdateEntityDescription(UpdateEntityDescription):
"""Class describing mikrotik entities."""
ha_group: str = ""
ha_connection: str = ""
ha_connection_value: str = ""
title: str = ""
data_path: str = ""
ha_group: str | None = None
ha_connection: str | None = None
ha_connection_value: str | None = None
title: str | None = None
data_path: str | None = None
data_attribute: str = "available"
data_name: str = ""
data_name: str | None = None
data_name_comment: bool = False
data_uid: str = ""
data_reference: str = ""
data_uid: str | None = None
data_reference: str | None = None
data_attributes_list: List = field(default_factory=lambda: [])
func: str = "MikrotikRouterOSUpdate"
SENSOR_TYPES = {
"system_rosupdate": MikrotikUpdateEntityDescription(
SENSOR_TYPES: tuple[MikrotikUpdateEntityDescription, ...] = (
MikrotikUpdateEntityDescription(
key="system_rosupdate",
name="RouterOS update",
ha_group="System",
@ -34,7 +38,7 @@ SENSOR_TYPES = {
data_reference="",
func="MikrotikRouterOSUpdate",
),
"system_rbfwupdate": MikrotikUpdateEntityDescription(
MikrotikUpdateEntityDescription(
key="system_rbfwupdate",
name="RouterBOARD firmware update",
ha_group="System",
@ -46,7 +50,7 @@ SENSOR_TYPES = {
data_reference="",
func="MikrotikRouterBoardFWUpdate",
),
}
)
SENSOR_SERVICES = {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View file

@ -1,6 +1,6 @@
{
"name": "Mikrotik Router",
"homeassistant": "2022.8.0",
"homeassistant": "2024.3.0",
"render_readme": false,
"zip_release": true,
"filename": "mikrotik_router.zip"

View file

@ -1,2 +1,2 @@
librouteros>=3.2.0
librouteros>=3.4.1
mac-vendor-lookup>=0.1.12

View file

@ -1,5 +1,5 @@
[flake8]
ignore = W293, # blank line contains whitespace
ignore = W293
max-line-length = 220
max-complexity = 10
@ -7,7 +7,7 @@ exclude = ./.git,
./tests,
./.github,
__pycache__,
./docs
./docs,
./custom_components/mikrotik_router/librouteros_custom
# Run with: pylint --rcfile=setup.cfg --load-plugins=pylint.extensions.mccabe custom_components
@ -19,4 +19,4 @@ disable = duplicate-code,
too-many-arguments,
too-many-instance-attributes,
simplifiable-if-expression,
#bare-except,
bare-except