Compare commits

...

229 commits
2.0 ... master

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
Tomaae
c45eff65e0
updated hacs manifest 2022-09-03 08:30:15 +02:00
Tomaae
6b4a92fcb1
updated docs 2022-09-02 23:49:55 +02:00
Tomaae
0af7bc184c
added wifi information sensors and binary sensors 2022-08-21 22:40:37 +02:00
Tomaae
c5570b6d5e
added wifi information to interfaces 2022-08-21 22:27:49 +02:00
Tomaae
5520b25c0f
removed old tests 2022-08-20 04:22:11 +02:00
Tomaae
d1d1d1a3b8
Updated stale workflow 2022-08-20 02:54:06 +02:00
Tomaae
e3f7073219
Switched stale to workflow 2022-08-20 02:52:37 +02:00
Tomaae
9667dcc171
Allow integration to run with limited access rights #235 2022-08-20 01:43:54 +02:00
Tomaae
58f931ad8e
Allow integration to run with limited access rights #235 2022-08-20 01:42:03 +02:00
Tomaae
ac789a81c2
Fixed script button for new entity naming 2022-08-20 01:07:38 +02:00
Tomaae
f487441038
Refactored model_async_setup_entry 2022-08-19 14:04:13 +02:00
Tomaae
dc2e804f0e
Added measurement state class to utilization sensors 2022-08-19 14:02:29 +02:00
Tomaae
e1d397f8e1
Added measurement state class to fans 2022-08-19 13:52:37 +02:00
Tomaae
717fa2f6ae
Removed redundant async_schedule_update_ha_state from model 2022-08-19 13:30:42 +02:00
Tomaae
1294631f8a
Updated model dummy services debugs 2022-08-19 11:43:01 +02:00
Tomaae
c468e2776e
Added async_will_remove_from_hass debug 2022-08-19 11:40:02 +02:00
Tomaae
952f3877b7
Replaced deprecated async_setup_platforms with async_forward_entry_setups 2022-08-18 10:45:31 +02:00
Tomaae
12a952a77d
Changed device tracker name return to None 2022-08-18 09:45:25 +02:00
Tomaae
1722a3e346
Removed unused import 2022-08-18 09:30:11 +02:00
Tomaae
0b4a7a3c72
Migrated to new entity naming style 2022-08-18 09:29:55 +02:00
Tomaae
1a0861fd9c
updated stale config 2022-08-18 01:05:02 +02:00
Tomaae
9711006f29
Merge remote-tracking branch 'origin/master' 2022-08-18 00:11:29 +02:00
Tomaae
575b7ff192
Removed duplicate debug entry 2022-08-18 00:06:40 +02:00
Tomaae
e4c48353c5
Merge pull request #237 from sermayoral/little-fixes-update-entities
Little fixes update entities
2022-08-17 18:32:06 +02:00
Sergio Mayoral Martinez
b2d36ebafc title implemented via entity descriptor 2022-08-17 16:23:41 +02:00
Sergio Mayoral Martinez
bd9009be47 Revert "Set update entity names like Supervisor entity names"
This reverts commit e22a4c1295.
2022-08-17 15:45:50 +02:00
Sergio Mayoral Martinez
34b373f3ee Black Formatting 2022-08-17 13:09:59 +02:00
Sergio Mayoral Martinez
e22a4c1295 Set update entity names like Supervisor entity names 2022-08-17 12:56:33 +02:00
Sergio Mayoral Martinez
e32ffd19a1 Move the firmware entity update to the right place 2022-08-17 12:54:26 +02:00
Sergio Mayoral Martinez
a588cec520 add title to update entities 2022-08-17 12:36:59 +02:00
Tomaae
d420a8ad06
Fixed duplication of tracker entities for restored entities #236 2022-08-17 09:29:46 +02:00
Tomaae
cbac88efd2
removed forgotten debug line 2022-08-16 14:37:37 +02:00
Tomaae
23f64fd401
updated docs 2022-08-16 00:45:58 +02:00
Tomaae
3b81c11675
updated docs 2022-08-16 00:43:35 +02:00
Tomaae
f8b8dd075b
refactoring 2022-08-11 17:02:46 +02:00
Tomaae
968d5c3859
Consolidated device tracker unique id, fixes #194 2022-08-11 16:45:35 +02:00
Tomaae
f4bffc55de
add update sensor for routerboard firmware #229 2022-08-11 14:58:30 +02:00
Tomaae
6a089556db
Improved error message when release notes fetch fails #229 2022-08-11 14:57:29 +02:00
Tomaae
4da561f515
renamed routeros update entity is for clarity #229 2022-08-11 14:52:17 +02:00
Tomaae
5d391cc09d
binary sensor cleanup #229 2022-08-11 14:50:45 +02:00
Tomaae
24e91ba115
Changed update entity name to RouterOS update #229 2022-08-11 13:33:57 +02:00
Tomaae
1a4479da78
Changed modem detection from route, to dhcp client. fixes #183 2022-08-11 13:23:51 +02:00
Tomaae
c639d65332
Replaced fwupdate with new HA update entity #229 2022-08-11 12:56:28 +02:00
Tomaae
5b1c88fa6a
fixed reconnect, fixes #217 2022-08-11 10:04:57 +02:00
Tomaae
b368ecc532
Merge pull request #215 from tomaae/imgbot
[ImgBot] Optimize images
2022-07-07 08:55:18 +02:00
ImgBotApp
52c87eaf3a
[ImgBot] Optimize images
*Total -- 662.60kb -> 526.18kb (20.59%)

/docs/assets/images/ui/integration_options_sensors.png -- 23.55kb -> 9.66kb (58.97%)
/docs/assets/images/ui/diagnostics.png -- 46.06kb -> 33.24kb (27.83%)
/docs/assets/images/ui/gps.png -- 29.95kb -> 23.52kb (21.47%)
/docs/assets/images/ui/queue_switch.png -- 47.37kb -> 37.71kb (20.4%)
/docs/assets/images/ui/interface_sensor.png -- 42.90kb -> 34.19kb (20.3%)
/docs/assets/images/ui/ups.png -- 42.13kb -> 33.61kb (20.22%)
/docs/assets/images/ui/ppp_tracker.png -- 25.93kb -> 20.79kb (19.81%)
/docs/assets/images/ui/interface.png -- 51.30kb -> 41.26kb (19.58%)
/docs/assets/images/ui/mangle_switch.png -- 38.21kb -> 30.83kb (19.3%)
/docs/assets/images/ui/interface_switch.png -- 50.48kb -> 40.75kb (19.28%)
/docs/assets/images/ui/ppp_switch.png -- 26.46kb -> 21.42kb (19.08%)
/docs/assets/images/ui/host_tracker.png -- 26.08kb -> 21.22kb (18.64%)
/docs/assets/images/ui/nat.png -- 34.67kb -> 28.25kb (18.52%)
/docs/assets/images/ui/accounting_sensor.png -- 21.17kb -> 17.26kb (18.45%)
/docs/assets/images/ui/kidcontrol_switch.png -- 31.41kb -> 25.87kb (17.63%)
/docs/assets/images/ui/kidcontrol_pause_switch.png -- 32.47kb -> 26.76kb (17.59%)
/docs/assets/images/ui/firmware_update.png -- 26.48kb -> 21.83kb (17.58%)
/docs/assets/images/ui/script_switch.png -- 32.67kb -> 27.10kb (17.05%)
/docs/assets/images/ui/setup_integration.png -- 16.22kb -> 14.56kb (10.21%)
/docs/assets/images/ui/integration_options.png -- 17.11kb -> 16.36kb (4.41%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2022-07-07 04:26:57 +00:00
Tomaae
b52ff90a88
docs, added fw update screenshot #213 2022-07-06 13:38:15 +02:00
Tomaae
1183c4371d
added version attributes to firmware update sensor #213 2022-07-06 13:33:49 +02:00
Tomaae
5408d60bbd
api cleanup 2022-06-30 22:20:45 +02:00
Tomaae
7d1915b566
fixed typo in captive portal #200 2022-06-30 13:48:22 +02:00
Tomaae
ce54b6ef1b
fixed typo in captive portal #200 2022-06-29 14:07:57 +02:00
Tomaae
f180337c33
docs, added gps preview image #196 2022-06-28 22:51:01 +02:00
Tomaae
3ee517463b
added GPS support #196 2022-06-28 21:57:24 +02:00
Tomaae
f1c7cea7c4
added UPS support #144 2022-06-28 21:26:49 +02:00
Tomaae
c10fb9fedb
API, renamed path and update functions 2022-06-28 20:28:00 +02:00
Tomaae
7782503f78
merged get_sfp into path 2022-06-28 20:13:07 +02:00
Tomaae
8efaa72492
API, option for path command to execute commands in path and pass arguments 2022-06-28 20:12:41 +02:00
Tomaae
a76417c240
Cleanup API 2022-06-25 15:18:14 +02:00
Tomaae
e14d018b20
Added captive portal authorized clients count #200 2022-06-25 15:18:00 +02:00
Tomaae
981668032e
Added sensors for total interface tx/rx bytes, fixes #210 2022-06-25 14:34:25 +02:00
Tomaae
574d3137b9
updated devices removal entries for future use 2022-06-25 13:56:39 +02:00
Tomaae
085a859dd7
updated entity registry access, fixes #209 2022-06-25 13:55:46 +02:00
Tomaae
bd56487f42
force check for firmware update, fixes #208 2022-05-27 10:58:51 +02:00
Tomaae
2c61ba596a
removed domains from hacs.json 2022-05-26 10:24:58 +02:00
Tomaae
9da682b314
added option to manually remove devices #198 2022-05-26 10:16:58 +02:00
Tomaae
bc127cd16e
Use Snake Case formatting for attribute names #204 2022-05-26 10:10:02 +02:00
Tomaae
ccb1b1fae7
Added now and unknown states to last seen tracker attribute #203 2022-05-26 10:06:06 +02:00
Tomaae
263b38b301
Changed last seen to datetime to improve DB size usage #203 2022-05-26 10:00:02 +02:00
Tomaae
6b6a240412
removed iot_class from hacs.json 2022-05-26 09:55:21 +02:00
Tomaae
c931681371
Merge pull request #207 from tomaae/lokalise-2022-05-26_09-45-07
Lokalise: Translations update
2022-05-26 09:45:47 +02:00
Tomaae
e4b9fdc278 Lokalise: updates 2022-05-26 09:45:14 +02:00
Tomaae
9f19740c92
Added support for captive portal authorization data #200 2022-05-26 09:41:00 +02:00
Tomaae
39498f073b
Merge pull request #205 from SamJongenelen/master
Added logging of the Filter ID
2022-05-24 08:47:17 +02:00
Sam Jongenelen
6306829bf8
Added logging of the Filter ID 2022-05-23 15:43:14 +02:00
Tomaae
817ecaa8c1
skip availability check when interface or address is empty #199 2022-05-18 22:04:37 +02:00
Tomaae
0da0f876c3
always update tracked host data from parent source #180 2022-05-18 21:28:53 +02:00
Tomaae
5bef491779
Updated docu #193 2022-04-16 19:38:00 +02:00
Tomaae
d558f00a3b
Merge pull request #192 from tomaae/lokalise-2022-04-11_10-33-38
Lokalise: Translations update
2022-04-11 10:34:53 +02:00
Tomaae
22f483e49f Lokalise: updates 2022-04-11 10:33:46 +02:00
Tomaae
84bd65c881
set all comments from API to str, fixes #180 2022-04-06 19:57:27 +02:00
Tomaae
a6040f7f38
updated strings 2022-04-06 18:45:54 +02:00
Tomaae
e8dde3bb52
fixed sensor exceptions in model 2022-04-06 17:37:55 +02:00
Tomaae
36a372db1f
Merge pull request #191 from tomaae/lokalise-2022-04-06_17-14-20
Lokalise: Translations update
2022-04-06 17:20:36 +02:00
Tomaae
ee1141c0a5 Lokalise: updates 2022-04-06 17:14:28 +02:00
Tomaae
379ee9fb55
updated strings 2022-04-06 17:08:43 +02:00
Tomaae
3b61faa03e
synced strings 2022-04-06 17:03:24 +02:00
Tomaae
c37b046a86
Merge pull request #190 from tomaae/lokalise-2022-04-06_16-46-45
Lokalise: Translations update
2022-04-06 16:59:58 +02:00
Tomaae
af4af1be14 Lokalise: updates 2022-04-06 16:46:53 +02:00
Tomaae
d8b073dac9
code cleanup 2022-04-06 16:20:06 +02:00
Tomaae
82098ba235
removed bare excepts 2022-04-06 16:19:56 +02:00
Tomaae
b57bb35eac
updated docs 2022-03-27 04:28:14 +02:00
Tomaae
9b5bdcb27d
updated docs 2022-03-27 04:26:08 +02:00
Tomaae
ddab2394ca
updated docs 2022-03-27 04:24:07 +02:00
Tomaae
56df9acabd
updated docs 2022-03-27 04:18:55 +02:00
Tomaae
b114a85bd7
updated docs 2022-03-27 04:13:20 +02:00
Tomaae
d863afd7ca
added support for per entity services
refactored HA entity implementation
2022-03-27 03:29:36 +02:00
Tomaae
40d98f4778
controller code cleanup 2022-03-26 01:38:46 +01:00
Tomaae
d1a8c624fb
init cleanup 2022-03-26 01:29:03 +01:00
Tomaae
0c097e2206
updated diagnostics 2022-03-26 01:26:30 +01:00
Tomaae
5c8a8d4870
synced json parser with other projects 2022-03-26 01:23:24 +01:00
Tomaae
20aadb2ec7
removed duplicate code 2022-03-23 15:19:21 +01:00
Tomaae
bd063ca8f0
Merge pull request #187 from mvdwetering/use_default_model_and_default_manufacturer
Use default_model and default_manufacturer
2022-03-23 15:18:34 +01:00
Michel van de Wetering
f20496b027 Use default_model and default_manufacturer 2022-03-23 14:07:36 +01:00
Tomaae
67d8a66066
Merge remote-tracking branch 'origin/master' 2022-03-15 10:31:54 +01:00
Tomaae
1259018e56
added local ip address to PPP connection tracking #184 2022-03-15 10:31:48 +01:00
Tomaae
b306420d47
Update lock.yml 2022-03-13 03:04:16 +01:00
Tomaae
74e6bd88e6
updated issue template 2022-03-12 12:42:36 +01:00
Tomaae
5e2f35b76c
updated issue template 2022-03-12 12:25:32 +01:00
Tomaae
f0f5c4c4a9 added HACS workflow 2022-03-09 22:04:09 +01:00
Tomaae
84fc3ff7f0 added support for CHR, ref #181 2022-03-09 15:06:30 +01:00
Tomaae
6a0b2e894a removed old code 2022-03-09 10:11:03 +01:00
Tomaae
6bec0efeb2 updated file descriptions 2022-03-04 14:10:19 +01:00
Tomaae
1280f9bee1 readme, added CI status and downloads badges 2022-03-04 14:08:06 +01:00
Tomaae
a49b52fb1a removed bandit config 2022-03-04 13:43:56 +01:00
Tomaae
6b4f967111 Prevent bandit B105 2022-03-04 13:40:55 +01:00
90 changed files with 3857 additions and 4750 deletions

View file

@ -33,15 +33,23 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
-->
## Software versions
<!--
All fields in this sections are required.
-->
- Home Assistant version: <!-- e.g. HA v0.108.3 -->
- Home Assistant version: <!-- e.g. HA 2022.2.0 -->
- Mikrotik Router integration version: <!-- e.g. v1.0.0 -->
- Mikrotik Hardware: <!-- e.g. RB4011iGS+ -->
- RouterOS version: <!-- e.g. v6.45 -->
## Diagnostics data
<!--
If you are seing incorrect data through the integration, please upload integration diagnostics data.
-->
## Traceback/Error logs
<!--
If you come across any trace or error logs, please provide them.

2
.github/bandit.yaml vendored
View file

@ -1,2 +0,0 @@
skips:
- B105 # :hardcoded_password_string

46
.github/stale.yml vendored
View file

@ -1,46 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 30
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- planned
- help wanted
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: true
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: false
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
# Handle pull requests a little bit faster and with an adjusted comment.
pulls:
daysUntilStale: 30
exemptProjects: false
markComment: >
There hasn't been any activity on this pull request recently. This pull
request has been automatically marked as stale because of that and will
be closed if no further activity occurs within 7 days.
Thank you for your contributions.

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

18
.github/workflows/hacs.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: HACS Action
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
hacs:
name: HACS Action
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- name: HACS Action
uses: "hacs/action@main"
with:
category: "integration"

View file

@ -3,6 +3,14 @@ name: Lock
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
concurrency:
group: lock
jobs:
lock:
@ -11,7 +19,9 @@ jobs:
- uses: dessant/lock-threads@v3.0.0
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: "30"
exclude-any-issue-labels: 'planned, help-wanted'
exclude-any-pr-labels: 'wip'
issue-inactive-days: "30"
issue-lock-reason: ""
pr-lock-inactive-days: "7"
pr-inactive-days: "7"
pr-lock-reason: ""

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: |

20
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,20 @@
name: 'Stale'
on:
schedule:
- cron: '30 1 * * *'
workflow_dispatch:
jobs:
stale:
name: Stale
runs-on: ubuntu-latest
steps:
- 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.'
close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.'
days-before-stale: 14
days-before-close: 7
exempt-issue-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

@ -2,9 +2,11 @@
![GitHub release (latest by date)](https://img.shields.io/github/v/release/tomaae/homeassistant-mikrotik_router?style=plastic)
[![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg?style=plastic)](https://github.com/hacs/integration)
![Project Stage](https://img.shields.io/badge/project%20stage-Production%20Ready-green.svg?style=plastic)
![GitHub all releases](https://img.shields.io/github/downloads/tomaae/homeassistant-mikrotik_router/total?style=plastic)
![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/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/)
@ -46,18 +48,21 @@ Monitor and control your Mikrotik device from Home Assistant.
* Enable/disable Mangle switches
* Enable/disable Filter switches
* Monitor and control PPP users
* Monitor UPS
* Monitor GPS coordinates
* Captive Portal
* Kid Control
* Client Traffic RX/TX WAN/LAN monitoring though Accounting or Kid Control Devices (depending on RouterOS FW version)
* Device tracker for hosts in network
* System sensors (CPU, Memory, HDD, Temperature)
* Check firmware update
* Check and update RouterOS and RouterBOARD firmware
* Execute scripts
* View environment variables
* Configurable update interval
* Configurable traffic unit (bps, Kbps, Mbps, B/s, KB/s, MB/s)
* Supports monitoring of multiple mikrotik devices simultaneously
## Features
# Features
## Interfaces
Monitor and control status on each Mikrotik interface, both lan and wlan. Both physical and virtual.
@ -90,7 +95,7 @@ NOTE: FastTracked packets are not processed by Simple Queues.
![Queue switch](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/queue_switch.png)
### PPP
## PPP
Control and monitor PPP users.
![PPP switch](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/ppp_switch.png)
@ -101,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.
@ -108,15 +118,14 @@ You can execute scripts by automatically created switches or using services.
![Script Switch](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/script_switch.png)
## Kid Control
Monitor and control.
Monitor and control Kid Control.
![Kid Control](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/kidcontrol_switch.png)
![Kid Control Enable](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/kidcontrol_switch.png)
![Kid Control Pause](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/kidcontrol_pause_switch.png)
## Client Traffic
#### RouterOS v6
###### Accounting
### Client Traffic for RouterOS v6
Monitor per-IP throughput tracking based on Mikrotik Accounting.
Feature is present in Winbox IP-Accounting. Make sure that threshold is set to reasonable value to store all connections between user defined scan interval. Max value is 8192 so for piece of mind I recommend setting that value.
@ -126,9 +135,7 @@ More information about Accounting can be found on [Mikrotik support page](https:
NOTE: Accounting does not count in FastTracked packets.
#### RouterOS v7
###### Kid Control Devices
### Client Traffic for RouterOS v7+
In RouterOS v7 Accounting feature is deprecated so alternative approach for is to use
Kid Control Devices feature (IP - Kid Control - Devices).
@ -141,6 +148,22 @@ Simple dummy Kid entry can be defined with
![Accounting sensor](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/accounting_sensor.png)
## UPS sensor
Monitor your UPS.
![UPS sensor](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/ups.png)
## GPS sensors
Monitor your GPS coordinates.
![GPS sensor](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/gps.png)
## Update sensor
Update Mikrotik OS and firmare directly from Home Assistant.
![RouterOS update](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/routeros_update.png)
![Firmware update](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/firmware_update.png)
# Install integration
This integration is distributed using [HACS](https://hacs.xyz/).
@ -156,9 +179,11 @@ 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, test, 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 & 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, test
* read, write
or check "Don't Require Permissions" option
3. Setup this integration for your Mikrotik device in Home Assistant via `Configuration -> Integrations -> Add -> Mikrotik Router`.
You can add this integration several times for different devices.
@ -174,13 +199,16 @@ NOTES:
## Configuration
First options page:
![Integration options](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/integration_options.png)
* "Scan interval" - Scan/refresh time in seconds. HA needs to be reloaded for scan interval change to be applied
* "Unit of measurement" - Traffic sensor measurement (bps, Kbps, Mbps, B/s, KB/s, MB/s)
* "Show client MAC and IP on interfaces" - Display connected IP and MAC address for devices connected to ports on router
* "Track network devices timeout" - Tracked devices will be marked as away after timeout (does not apply to Mikrotik wireless and caps-man)
* "Zone for device tracker" - Add new tracked devices to a specified Home Assistant zone
Second options page:
![Integration sensors](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/integration_options_sensors.png)
Select sensors you want to use in Home Assistant.
@ -193,6 +221,11 @@ After you have created your account [click here to join Mikrotik Router project
If you want to add translations for a language that is not listed please [open a Feature request](https://github.com/tomaae/homeassistant-mikrotik_router/issues/new?labels=enhancement&title=%5BLokalise%5D%20Add%20new%20translations%20language).
## Diagnostics
Download diagnostics data for investigation:
![Diagnostics](https://raw.githubusercontent.com/tomaae/homeassistant-mikrotik_router/master/docs/assets/images/ui/diagnostics.png)
## Enabling debug
To enable debug for Mikrotik router integration, add following to your configuration.yaml:
```

View file

@ -1,79 +1,98 @@
"""Mikrotik Router integration."""
from __future__ import annotations
import voluptuous as vol
import logging
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import ConfigEntryNotReady
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 .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(f"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
hass.config_entries.async_setup_platforms(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
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(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
# ---------------------------
# async_remove_config_entry_device
# ---------------------------
async def async_remove_config_entry_device(
hass, config_entry: ConfigEntry, device_entry: device_registry.DeviceEntry
) -> 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,77 +1,83 @@
"""API parser functions for Mikrotik Router."""
"""API parser for JSON APIs."""
import logging
from datetime import datetime
from logging import getLogger
from pytz import utc
from voluptuous import Optional
from homeassistant.components.diagnostics import async_redact_data
_LOGGER = logging.getLogger(__name__)
from .const import TO_REDACT
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",
}
_LOGGER = getLogger(__name__)
# ---------------------------
# utc_from_timestamp
# ---------------------------
def utc_from_timestamp(timestamp: float) -> datetime:
"""Return a UTC time from a timestamp."""
return utc.localize(datetime.utcfromtimestamp(timestamp))
# ---------------------------
# from_entry
# ---------------------------
def from_entry(entry, param, default="") -> str:
"""Validate and return str value from Mikrotik API dict"""
if param not in entry:
"""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:
entry = entry[tmp_param]
else:
return default
ret = entry
elif param in entry:
ret = entry[param]
else:
return default
return (
entry[param][:255]
if isinstance(entry[param], str) and len(entry[param]) > 255
else entry[param]
)
if default != "":
if isinstance(ret, str):
ret = str(ret)
elif isinstance(ret, int):
ret = int(ret)
elif isinstance(ret, float):
ret = round(float(ret), 2)
return ret[:255] if isinstance(ret, str) and len(ret) > 255 else ret
# ---------------------------
# from_entry_bool
# ---------------------------
def from_entry_bool(entry, param, default=False, reverse=False) -> bool:
"""Validate and return a bool value from a Mikrotik API dict"""
if param not in entry:
return default
"""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:
entry = entry[tmp_param]
else:
return default
if not reverse:
ret = entry
elif param in entry:
ret = entry[param]
else:
if entry[param]:
ret = False
else:
ret = True
return default
return ret
if isinstance(ret, str):
if ret in ("on", "On", "ON", "yes", "Yes", "YES", "up", "Up", "UP"):
ret = True
elif ret in ("off", "Off", "OFF", "no", "No", "NO", "down", "Down", "DOWN"):
ret = False
if not isinstance(ret, bool):
ret = default
return not ret if reverse else ret
# ---------------------------
@ -89,15 +95,17 @@ def parse_api(
only=None,
skip=None,
) -> dict:
"""Get data from API"""
debug = False
if _LOGGER.getEffectiveLevel() == 10:
debug = True
"""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:
data = fill_defaults(data, vals)
return data
if debug:
_LOGGER.debug("Processing source %s", async_redact_data(source, TO_REDACT))
@ -137,13 +145,10 @@ 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 = True
if key not in entry:
key_primary_found = False
key_primary_found = key in entry
if key_primary_found and key not in entry and not entry[key]:
return None
@ -157,38 +162,31 @@ def get_uid(entry, key, key_secondary, key_search, keymap) -> Optional(str):
return None
uid = entry[key_secondary]
elif keymap and key_search in entry and entry[key_search] in keymap:
uid = keymap[entry[key_search]]
else:
if keymap and key_search in entry and entry[key_search] in keymap:
uid = keymap[entry[key_search]]
else:
return None
return None
return uid if uid else None
return uid or None
# ---------------------------
# generate_keymap
# ---------------------------
def generate_keymap(data, key_search) -> Optional(dict):
"""Generate keymap"""
if not key_search:
return None
keymap = {}
for uid in data:
if key_search not in data[uid]:
continue
keymap[data[uid][key_search]] = uid
return keymap
"""Generate keymap."""
return (
{data[uid][key_search]: uid for uid in data if key_search in data[uid]}
if key_search
else None
)
# ---------------------------
# 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"]:
@ -204,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"]:
@ -222,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"
@ -251,11 +249,12 @@ 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"
_source = val["source"] if "source" in val else _name
_convert = val["convert"] if "convert" in val else None
if _type == "str":
_default = val["default"] if "default" in val else ""
@ -280,6 +279,19 @@ def fill_vals(data, entry, uid, vals) -> dict:
entry, _source, default=_default, reverse=_reverse
)
if _convert == "utc_from_timestamp":
if uid:
if isinstance(data[uid][_name], int) and data[uid][_name] > 0:
if data[uid][_name] > 100000000000:
data[uid][_name] = data[uid][_name] / 1000
data[uid][_name] = utc_from_timestamp(data[uid][_name])
elif isinstance(data[_name], int) and data[_name] > 0:
if data[_name] > 100000000000:
data[_name] = data[_name] / 1000
data[_name] = utc_from_timestamp(data[_name])
return data
@ -287,16 +299,16 @@ 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]:
_default = val["default"] if "default" in val else ""
data[uid][val["name"]] = _default
else:
if val["name"] not in data:
_default = val["default"] if "default" in val else ""
data[val["name"]] = _default
elif val["name"] not in data:
_default = val["default"] if "default" in val else ""
data[val["name"]] = _default
return data
@ -305,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
@ -326,17 +338,11 @@ def fill_vals_proc(data, uid, vals_proc) -> dict:
if _action == "combine":
if "key" in val:
tmp = _data[val["key"]] if val["key"] in _data else "unknown"
if not _value:
_value = tmp
else:
_value = f"{_value}{tmp}"
_value = f"{_value}{tmp}" if _value else tmp
if "text" in val:
tmp = val["text"]
if not _value:
_value = tmp
else:
_value = f"{_value}{tmp}"
_value = f"{_value}{tmp}" if _value else tmp
if _name and _value:
if uid:

View file

@ -1,332 +1,80 @@
"""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.helpers.entity import DeviceInfo
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
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 homeassistant.const import (
CONF_NAME,
CONF_HOST,
ATTR_ATTRIBUTION,
)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .helper import format_attribute
from .const import (
DOMAIN,
ATTRIBUTION,
CONF_SENSOR_PPP,
DEFAULT_SENSOR_PPP,
CONF_SENSOR_PORT_TRACKER,
DEFAULT_SENSOR_PORT_TRACKER,
CONF_SENSOR_NETWATCH_TRACKER,
DEFAULT_SENSOR_NETWATCH_TRACKER,
)
from .binary_sensor_types import (
MikrotikBinarySensorEntityDescription,
SENSOR_TYPES,
DEVICE_ATTRIBUTES_IFACE_ETHER,
DEVICE_ATTRIBUTES_IFACE_SFP,
)
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):
"""Set up device tracker for Mikrotik Router component."""
inst = config_entry.data[CONF_NAME]
mikrotik_controller = hass.data[DOMAIN][config_entry.entry_id]
sensors = {}
@callback
def update_controller():
"""Update the values of the controller."""
update_items(
inst, config_entry, mikrotik_controller, async_add_entities, sensors
)
mikrotik_controller.listeners.append(
async_dispatcher_connect(
hass, mikrotik_controller.signal_update, update_controller
)
)
update_controller()
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 async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
# update_items
# MikrotikBinarySensor
# ---------------------------
@callback
def update_items(inst, config_entry, mikrotik_controller, async_add_entities, sensors):
"""Update sensor state from the controller."""
new_sensors = []
for sensor, sid_func in zip(
# Sensor type name
["ppp_tracker", "interface"],
# Entity function
[MikrotikControllerPPPSecretBinarySensor, MikrotikControllerPortBinarySensor],
):
if sensor == "interface" and not config_entry.options.get(
CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER
):
continue
uid_sensor = SENSOR_TYPES[sensor]
for uid in mikrotik_controller.data[uid_sensor.data_path]:
uid_data = mikrotik_controller.data[uid_sensor.data_path]
if uid_sensor.data_path == "interface" and uid_data[uid]["type"] == "wlan":
continue
item_id = f"{inst}-{sensor}-{uid_data[uid][uid_sensor.data_reference]}"
_LOGGER.debug("Updating binary sensor %s", item_id)
if item_id in sensors:
if sensors[item_id].enabled:
sensors[item_id].async_schedule_update_ha_state()
continue
sensors[item_id] = sid_func(
inst=inst,
uid=uid,
mikrotik_controller=mikrotik_controller,
entity_description=uid_sensor,
config_entry=config_entry,
)
new_sensors.append(sensors[item_id])
for sensor in SENSOR_TYPES:
if sensor.startswith("system_"):
uid_sensor = SENSOR_TYPES[sensor]
item_id = f"{inst}-{sensor}"
_LOGGER.debug("Updating binary sensor %s", item_id)
if item_id in sensors:
if sensors[item_id].enabled:
sensors[item_id].async_schedule_update_ha_state()
continue
sensors[item_id] = MikrotikControllerBinarySensor(
inst=inst,
uid="",
mikrotik_controller=mikrotik_controller,
entity_description=uid_sensor,
config_entry=config_entry,
)
new_sensors.append(sensors[item_id])
#
# # Add switches
# for sid, sid_uid, sid_name, sid_ref, sid_attr, sid_func in zip(
# # Data point name
# ["ppp_secret", "interface"],
# # Data point unique id
# ["name", "default-name"],
# # Entry Name
# ["name", "name"],
# # Entry Unique id
# ["name", "port-mac-address"],
# # Attr
# [None, DEVICE_ATTRIBUTES_IFACE],
# # Tracker function
# [
# MikrotikControllerPPPSecretBinarySensor,
# MikrotikControllerPortBinarySensor,
# ],
# ):
# if (
# sid_func == MikrotikControllerPortBinarySensor
# and not config_entry.options.get(
# CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER
# )
# ):
# continue
# for uid in mikrotik_controller.data[sid]:
# if (
# # Skip if interface is wlan
# sid == "interface"
# and mikrotik_controller.data[sid][uid]["type"] == "wlan"
# ):
# continue
# # Update entity
# item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}"
# _LOGGER.debug("Updating binary_sensor %s", item_id)
# if item_id in sensors:
# if sensors[item_id].enabled:
# sensors[item_id].async_schedule_update_ha_state()
# continue
#
# # Create new entity
# sid_data = {
# "sid": sid,
# "sid_uid": sid_uid,
# "sid_name": sid_name,
# "sid_ref": sid_ref,
# "sid_attr": sid_attr,
# }
# sensors[item_id] = sid_func(
# inst, uid, mikrotik_controller, config_entry, sid_data
# )
# new_sensors.append(sensors[item_id])
#
# for sensor in SENSOR_TYPES:
# item_id = f"{inst}-{sensor}"
# _LOGGER.debug("Updating binary_sensor %s", item_id)
# if item_id in sensors:
# if sensors[item_id].enabled:
# sensors[item_id].async_schedule_update_ha_state()
# continue
#
# sensors[item_id] = MikrotikControllerBinarySensor(
# mikrotik_controller=mikrotik_controller, inst=inst, sid_data=sensor
# )
# new_sensors.append(sensors[item_id])
if new_sensors:
async_add_entities(new_sensors, True)
class MikrotikControllerBinarySensor(BinarySensorEntity):
class MikrotikBinarySensor(MikrotikEntity, BinarySensorEntity):
"""Define an Mikrotik Controller Binary Sensor."""
def __init__(
self,
inst,
uid: "",
mikrotik_controller,
entity_description: MikrotikBinarySensorEntityDescription,
config_entry,
):
"""Initialize."""
self.entity_description = entity_description
self._config_entry = config_entry
self._inst = inst
self._ctrl = mikrotik_controller
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."""
if self._uid:
if self.entity_description.name:
return f"{self._inst} {self._data[self.entity_description.data_name]} {self.entity_description.name}"
return f"{self._inst} {self._data[self.entity_description.data_name]}"
else:
return f"{self._inst} {self.entity_description.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}-{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 is_on(self) -> bool:
"""Return true if device is on."""
return self._data[self.entity_description.data_is_on]
return self._data[self.entity_description.data_attribute]
@property
def icon(self) -> str:
"""Return the icon."""
if self.entity_description.icon_enabled:
if self._data[self.entity_description.data_is_on]:
if self._data[self.entity_description.data_attribute]:
return self.entity_description.icon_enabled
else:
return self.entity_description.icon_disabled
@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}",
model=f"{self._ctrl.data['resource']['board-name']}",
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}",
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):
"""Run when entity about to be added to hass."""
_LOGGER.debug("New binary sensor %s (%s)", self._inst, self.unique_id)
# ---------------------------
# MikrotikControllerPPPSecretBinarySensor
# MikrotikPPPSecretBinarySensor
# ---------------------------
class MikrotikControllerPPPSecretBinarySensor(MikrotikControllerBinarySensor):
class MikrotikPPPSecretBinarySensor(MikrotikBinarySensor):
"""Representation of a network device."""
@property
@ -337,24 +85,22 @@ class MikrotikControllerPPPSecretBinarySensor(MikrotikControllerBinarySensor):
@property
def is_on(self) -> bool:
"""Return true if device is on."""
if not self.option_sensor_ppp:
return False
return (
self._data[self.entity_description.data_attribute]
if self.option_sensor_ppp
else False
)
return self._data[self.entity_description.data_is_on]
@property
def available(self) -> bool:
"""Return if controller is available."""
if not self.option_sensor_ppp:
return False
return self._ctrl.connected()
# @property
# def available(self) -> bool:
# """Return if controller is available."""
# return self._ctrl.connected() if self.option_sensor_ppp else False
# ---------------------------
# MikrotikControllerPortBinarySensor
# MikrotikPortBinarySensor
# ---------------------------
class MikrotikControllerPortBinarySensor(MikrotikControllerBinarySensor):
class MikrotikPortBinarySensor(MikrotikBinarySensor):
"""Representation of a network port."""
@property
@ -364,18 +110,15 @@ class MikrotikControllerPortBinarySensor(MikrotikControllerBinarySensor):
CONF_SENSOR_PORT_TRACKER, DEFAULT_SENSOR_PORT_TRACKER
)
@property
def available(self) -> bool:
"""Return if controller is available."""
if not self.option_sensor_port_tracker:
return False
return self._ctrl.connected()
# @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:
"""Return the icon."""
if self._data[self.entity_description.data_is_on]:
if self._data[self.entity_description.data_attribute]:
icon = self.entity_description.icon_enabled
else:
icon = self.entity_description.icon_disabled
@ -400,4 +143,9 @@ class MikrotikControllerPortBinarySensor(MikrotikControllerBinarySensor):
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
elif self._data["type"] == "wlan":
for variable in DEVICE_ATTRIBUTES_IFACE_WIRELESS:
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
return attributes

View file

@ -1,4 +1,7 @@
"""Definitions for Mikrotik Router sensor entities."""
"""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
@ -66,39 +69,92 @@ DEVICE_ATTRIBUTES_IFACE_SFP = [
"eeprom-checksum",
]
DEVICE_ATTRIBUTES_IFACE_WIRELESS = [
"ssid",
"mode",
"radio-name",
"interface-type",
"country",
"installation",
"antenna-gain",
"frequency",
"band",
"channel-width",
"secondary-frequency",
"wireless-protocol",
"rate-set",
"distance",
"tx-power-mode",
"vlan-id",
"wds-mode",
"wds-default-bridge",
"bridge-mode",
"hide-ssid",
]
DEVICE_ATTRIBUTES_UPS = [
"name",
"offline-time",
"min-runtime",
"alarm-setting",
"model",
"serial",
"manufacture-date",
"nominal-battery-voltage",
"runtime-left",
"battery-charge",
"battery-voltage",
"line-voltage",
"load",
"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 = ""
data_is_on: str = "available"
data_name: str = ""
data_uid: str = ""
data_reference: 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 | 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 = "MikrotikBinarySensor"
SENSOR_TYPES = {
"system_fwupdate": MikrotikBinarySensorEntityDescription(
key="system_fwupdate",
name="Firmware update",
SENSOR_TYPES: tuple[BinarySensorEntityDescription, ...] = (
MikrotikBinarySensorEntityDescription(
key="system_ups",
name="UPS",
icon_enabled="",
icon_disabled="",
device_class=BinarySensorDeviceClass.UPDATE,
device_class=BinarySensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="fw-update",
data_name="",
data_path="ups",
data_attribute="on-line",
data_uid="",
data_reference="",
data_attributes_list=DEVICE_ATTRIBUTES_UPS,
),
"ppp_tracker": MikrotikBinarySensorEntityDescription(
MikrotikBinarySensorEntityDescription(
key="ppp_tracker",
name="PPP",
icon_enabled="mdi:account-network-outline",
@ -108,15 +164,16 @@ SENSOR_TYPES = {
ha_connection=DOMAIN,
ha_connection_value="PPP",
data_path="ppp_secret",
data_is_on="connected",
data_attribute="connected",
data_name="name",
data_uid="name",
data_reference="name",
data_attributes_list=DEVICE_ATTRIBUTES_PPP_SECRET,
func="MikrotikPPPSecretBinarySensor",
),
"interface": MikrotikBinarySensorEntityDescription(
MikrotikBinarySensorEntityDescription(
key="interface",
name="",
name="Connection",
icon_enabled="mdi:lan-connect",
icon_disabled="mdi:lan-pending",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
@ -124,10 +181,31 @@ SENSOR_TYPES = {
ha_connection=CONNECTION_NETWORK_MAC,
ha_connection_value="data__port-mac-address",
data_path="interface",
data_is_on="running",
data_name="name",
data_attribute="running",
data_name="default-name",
data_uid="default-name",
data_reference="default-name",
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,199 +1,62 @@
"""Support for the Mikrotik Router buttons."""
import logging
from typing import Any, Dict
from __future__ import annotations
from logging import getLogger
from homeassistant.components.button import ButtonEntity
from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.restore_state import RestoreEntity
from .helper import format_attribute
from .const import DOMAIN, ATTRIBUTION
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
_LOGGER = logging.getLogger(__name__)
from .entity import MikrotikEntity, async_add_entities
from .button_types import (
SENSOR_TYPES,
SENSOR_SERVICES,
)
from .exceptions import ApiEntryNotFound
DEVICE_ATTRIBUTES_SCRIPT = [
"last-started",
"run-count",
]
_LOGGER = getLogger(__name__)
# ---------------------------
# async_setup_entry
# ---------------------------
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up buttons for Mikrotik Router component."""
inst = config_entry.data[CONF_NAME]
mikrotik_controller = hass.data[DOMAIN][config_entry.entry_id]
buttons = {}
@callback
def update_controller():
"""Update the values of the controller."""
update_items(inst, mikrotik_controller, async_add_entities, buttons)
mikrotik_controller.listeners.append(
async_dispatcher_connect(
hass, mikrotik_controller.signal_update, update_controller
)
)
update_controller()
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 async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
# update_items
# MikrotikButton
# ---------------------------
@callback
def update_items(inst, mikrotik_controller, async_add_entities, buttons):
"""Update device button state from the controller."""
new_buttons = []
# Add buttons
for sid, sid_uid, sid_name, sid_ref, sid_attr, sid_func in zip(
# Data point name
[
"script",
],
# Data point unique id
[
"name",
],
# Entry Name
[
"name",
],
# Entry Unique id
[
"name",
],
# Attr
[
DEVICE_ATTRIBUTES_SCRIPT,
],
# Button function
[
MikrotikControllerScriptButton,
],
):
for uid in mikrotik_controller.data[sid]:
item_id = f"{inst}-{sid}-{mikrotik_controller.data[sid][uid][sid_uid]}"
_LOGGER.debug("Updating button %s", item_id)
if item_id in buttons:
if buttons[item_id].enabled:
buttons[item_id].async_schedule_update_ha_state()
continue
# Create new entity
sid_data = {
"sid": sid,
"sid_uid": sid_uid,
"sid_name": sid_name,
"sid_ref": sid_ref,
"sid_attr": sid_attr,
}
buttons[item_id] = sid_func(inst, uid, mikrotik_controller, sid_data)
new_buttons.append(buttons[item_id])
if new_buttons:
async_add_entities(new_buttons)
# ---------------------------
# MikrotikControllerButton
# ---------------------------
class MikrotikControllerButton(ButtonEntity, RestoreEntity):
class MikrotikButton(MikrotikEntity, ButtonEntity):
"""Representation of a button."""
def __init__(self, inst, uid, mikrotik_controller, sid_data):
"""Initialize."""
self._sid_data = sid_data
self._inst = inst
self._ctrl = mikrotik_controller
self._data = mikrotik_controller.data[self._sid_data["sid"]][uid]
self._attrs = {
ATTR_ATTRIBUTION: ATTRIBUTION,
}
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
_LOGGER.debug(
"New button %s (%s %s)",
self._inst,
self._sid_data["sid"],
self._data[self._sid_data["sid_uid"]],
)
async def async_update(self):
"""Synchronize state with controller."""
@property
def available(self) -> bool:
"""Return if controller is available."""
return self._ctrl.connected()
@property
def name(self) -> str:
"""Return the name."""
return f"{self._inst} {self._sid_data['sid']} {self._data[self._sid_data['sid_name']]}"
@property
def unique_id(self) -> str:
"""Return a unique id for this entity."""
return f"{self._inst.lower()}-{self._sid_data['sid']}_button-{self._data[self._sid_data['sid_ref']]}"
@property
def extra_state_attributes(self) -> Dict[str, Any]:
"""Return the state attributes."""
attributes = self._attrs
for variable in self._sid_data["sid_attr"]:
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
return attributes
async def async_press(self) -> None:
pass
# ---------------------------
# MikrotikControllerScriptButton
# MikrotikScriptButton
# ---------------------------
class MikrotikControllerScriptButton(MikrotikControllerButton):
class MikrotikScriptButton(MikrotikButton):
"""Representation of a script button."""
def __init__(self, inst, uid, mikrotik_controller, sid_data):
"""Initialize."""
super().__init__(inst, uid, mikrotik_controller, sid_data)
@property
def icon(self) -> str:
"""Return the icon."""
return "mdi:script-text-outline"
@property
def device_info(self) -> Dict[str, Any]:
"""Return a description for device registry."""
info = {
"identifiers": {
(
DOMAIN,
"serial-number",
f"{self._ctrl.data['routerboard']['serial-number']}",
"button",
"Scripts",
)
},
"manufacturer": self._ctrl.data["resource"]["platform"],
"model": self._ctrl.data["resource"]["board-name"],
"name": f"{self._inst} Scripts",
}
return info
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

@ -0,0 +1,56 @@
"""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
DEVICE_ATTRIBUTES_SCRIPT = [
"last-started",
"run-count",
]
@dataclass
class MikrotikButtonEntityDescription(SensorEntityDescription):
"""Class describing mikrotik entities."""
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 | None = None
data_reference: str | None = None
data_attributes_list: List = field(default_factory=lambda: [])
func: str = "MikrotikButton"
SENSOR_TYPES: tuple[MikrotikButtonEntityDescription, ...] = (
MikrotikButtonEntityDescription(
key="script",
name="",
icon="mdi:script-text-outline",
device_class=None,
entity_category=None,
ha_group="Script",
ha_connection=DOMAIN,
ha_connection_value="Script",
data_path="script",
data_name="name",
data_uid="name",
data_reference="name",
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,
)
@ -35,6 +35,8 @@ from .const import (
DEFAULT_SENSOR_PORT_TRAFFIC,
CONF_SENSOR_CLIENT_TRAFFIC,
DEFAULT_SENSOR_CLIENT_TRAFFIC,
CONF_SENSOR_CLIENT_CAPTIVE,
DEFAULT_SENSOR_CLIENT_CAPTIVE,
CONF_SENSOR_SIMPLE_QUEUES,
DEFAULT_SENSOR_SIMPLE_QUEUES,
CONF_SENSOR_NAT,
@ -53,14 +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_PASSWORD,
DEFAULT_PORT,
DEFAULT_DEVICE_NAME,
DEFAULT_SSL,
DEFAULT_VERIFY_SSL,
DEFAULT_SENSOR_NETWATCH_TRACKER,
CONF_SENSOR_NETWATCH_TRACKER,
)
from .mikrotikapi import MikrotikAPI
@ -84,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):
@ -115,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
@ -132,9 +135,10 @@ class MikrotikControllerConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_NAME: DEFAULT_DEVICE_NAME,
CONF_HOST: DEFAULT_HOST,
CONF_USERNAME: DEFAULT_USERNAME,
CONF_PASSWORD: DEFAULT_PASSWORD,
CONF_PASSWORD: DEFAULT_USERNAME,
CONF_PORT: DEFAULT_PORT,
CONF_SSL: DEFAULT_SSL,
CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL,
},
errors=errors,
)
@ -154,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,
@ -183,6 +190,7 @@ class MikrotikControllerOptionsFlowHandler(OptionsFlow):
return self.async_show_form(
step_id="basic_options",
last_step=False,
data_schema=vol.Schema(
{
vol.Optional(
@ -191,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(
@ -251,6 +253,12 @@ class MikrotikControllerOptionsFlowHandler(OptionsFlow):
CONF_SENSOR_CLIENT_TRAFFIC, DEFAULT_SENSOR_CLIENT_TRAFFIC
),
): bool,
vol.Optional(
CONF_SENSOR_CLIENT_CAPTIVE,
default=self.config_entry.options.get(
CONF_SENSOR_CLIENT_CAPTIVE, DEFAULT_SENSOR_CLIENT_CAPTIVE
),
): bool,
vol.Optional(
CONF_SENSOR_SIMPLE_QUEUES,
default=self.config_entry.options.get(
@ -281,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,59 +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,
]
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_PASSWORD = ""
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_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
"""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,151 +1,139 @@
"""Support for the Mikrotik Router device tracker."""
import logging
from typing import Any, Dict
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.const import (
CONF_NAME,
CONF_HOST,
ATTR_ATTRIBUTION,
STATE_NOT_HOME,
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.core import callback
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.util.dt import get_age, utcnow
from .helper import format_attribute, format_value
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,
ATTRIBUTION,
CONF_TRACK_HOSTS,
DEFAULT_TRACK_HOSTS,
CONF_TRACK_HOSTS_TIMEOUT,
DEFAULT_TRACK_HOST_TIMEOUT,
)
from .device_tracker_types import (
MikrotikDeviceTrackerEntityDescription,
SENSOR_TYPES,
)
_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):
"""Set up device tracker for Mikrotik Router component."""
inst = config_entry.data[CONF_NAME]
mikrotik_controller = hass.data[DOMAIN][config_entry.entry_id]
trackers = {}
@callback
def update_controller():
"""Update the values of the controller."""
update_items(
inst, config_entry, mikrotik_controller, async_add_entities, trackers
)
mikrotik_controller.listeners.append(
async_dispatcher_connect(
hass, mikrotik_controller.signal_update, update_controller
)
)
update_controller()
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 async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
# update_items
# MikrotikDeviceTracker
# ---------------------------
@callback
def update_items(inst, config_entry, mikrotik_controller, async_add_entities, trackers):
"""Update trackers device state from the controller."""
new_trackers = []
for sensor, sid_func in zip(
# Sensor type name
["host"],
# Entity function
[MikrotikControllerHostDeviceTracker],
):
uid_sensor = SENSOR_TYPES[sensor]
if (
# Skip if host tracking is disabled
sensor == "host"
and not config_entry.options.get(CONF_TRACK_HOSTS, DEFAULT_TRACK_HOSTS)
):
continue
for uid in mikrotik_controller.data[uid_sensor.data_path]:
uid_data = mikrotik_controller.data[uid_sensor.data_path]
item_id = f"{inst}-{sensor}-{uid_data[uid][uid_sensor.data_reference]}"
_LOGGER.debug("Updating device tracker %s", item_id)
if item_id in trackers:
if trackers[item_id].enabled:
trackers[item_id].async_schedule_update_ha_state()
continue
trackers[item_id] = sid_func(
inst=inst,
uid=uid,
mikrotik_controller=mikrotik_controller,
entity_description=uid_sensor,
config_entry=config_entry,
)
new_trackers.append(trackers[item_id])
# Register new entities
if new_trackers:
async_add_entities(new_trackers)
# ---------------------------
# MikrotikControllerDeviceTracker
# ---------------------------
class MikrotikControllerDeviceTracker(ScannerEntity):
class MikrotikDeviceTracker(MikrotikEntity, ScannerEntity):
"""Representation of a device tracker."""
def __init__(
self,
inst,
uid: "",
mikrotik_controller,
entity_description: MikrotikDeviceTrackerEntityDescription,
config_entry,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
"""Initialize."""
self.entity_description = entity_description
self._config_entry = config_entry
self._inst = inst
self._ctrl = mikrotik_controller
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._data = mikrotik_controller.data[self.entity_description.data_path][uid]
@property
def name(self) -> str:
"""Return the name."""
if self.entity_description.name:
return f"{self._inst} {self._data[self.entity_description.data_name]} {self.entity_description.name}"
return f"{self._inst} {self._data[self.entity_description.data_name]}"
@property
def unique_id(self) -> str:
"""Return a unique id for this entity."""
return f"{self._inst.lower()}-{self.entity_description.key}-{self._data[self.entity_description.data_reference].lower()}"
"""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."""
if "address" in self._data:
return self._data["address"]
return None
return self._data["address"] if "address" in self._data else None
@property
def mac_address(self) -> str:
@ -153,7 +141,7 @@ class MikrotikControllerDeviceTracker(ScannerEntity):
if self.entity_description.data_reference in self._data:
return self._data[self.entity_description.data_reference]
return None
return ""
@property
def hostname(self) -> str:
@ -161,105 +149,23 @@ class MikrotikControllerDeviceTracker(ScannerEntity):
if self.entity_description.data_name in self._data:
return self._data[self.entity_description.data_name]
return None
@property
def device_info(self) -> Dict[str, Any]:
"""Return a description for device registry."""
info = {
"connections": {
(CONNECTION_NETWORK_MAC, self._data[self._sid_data["sid_ref"]])
},
"manufacturer": self._ctrl.data["resource"]["platform"],
"model": self._ctrl.data["resource"]["board-name"],
"name": self._data[self._sid_data["sid_name"]],
}
if self._sid_data["sid"] == "interface":
info["name"] = f"{self._inst} {self._data[self._sid_data['sid_name']]}"
return info
return ""
@property
def is_connected(self) -> bool:
"""Return true if device is connected."""
return self._data[self.entity_description.data_is_on]
@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.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}",
model=f"{self._ctrl.data['resource']['board-name']}",
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}",
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
return self._data[self.entity_description.data_attribute]
@property
def source_type(self) -> str:
"""Return the source type of the port."""
return SOURCE_TYPE_ROUTER
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
_LOGGER.debug("New device tracker %s (%s)", self._inst, self.unique_id)
return SourceType.ROUTER
# ---------------------------
# MikrotikControllerHostDeviceTracker
# MikrotikHostDeviceTracker
# ---------------------------
class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker):
class MikrotikHostDeviceTracker(MikrotikDeviceTracker):
"""Representation of a network device."""
@property
@ -275,16 +181,6 @@ class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker):
)
return timedelta(seconds=track_network_hosts_timeout)
@property
def name(self) -> str:
"""Return the name."""
return f"{self._data[self.entity_description.data_name]}"
@property
def unique_id(self) -> str:
"""Return a unique id for this entity."""
return f"{self._data[self.entity_description.data_reference].lower()}"
@property
def is_connected(self) -> bool:
"""Return true if the host is connected to the network."""
@ -292,22 +188,19 @@ class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker):
return False
if self._data["source"] in ["capsman", "wireless"]:
return self._data[self.entity_description.data_is_on]
return self._data[self.entity_description.data_attribute]
if (
return bool(
self._data["last-seen"]
and (utcnow() - self._data["last-seen"])
and utcnow() - self._data["last-seen"]
< self.option_track_network_hosts_timeout
):
return True
return False
)
@property
def icon(self) -> str:
"""Return the icon."""
if self._data["source"] in ["capsman", "wireless"]:
if self._data[self.entity_description.data_is_on]:
if self._data[self.entity_description.data_attribute]:
return self.entity_description.icon_enabled
else:
return self.entity_description.icon_disabled
@ -323,17 +216,16 @@ class MikrotikControllerHostDeviceTracker(MikrotikControllerDeviceTracker):
@property
def state(self) -> str:
"""Return the state of the device."""
if self.is_connected:
return self._ctrl.option_zone
return STATE_NOT_HOME
return self.coordinator.option_zone if self.is_connected else STATE_NOT_HOME
@property
def extra_state_attributes(self) -> Dict[str, Any]:
def extra_state_attributes(self) -> Mapping[str, Any]:
"""Return the state attributes."""
attributes = super().extra_state_attributes
if self._data["last-seen"]:
attributes[format_attribute("last-seen")] = get_age(self._data["last-seen"])
else:
attributes[format_attribute("last-seen")] = "unknown"
if self.is_connected:
attributes[format_attribute("last-seen")] = "Now"
if not attributes[format_attribute("last-seen")]:
attributes[format_attribute("last-seen")] = "Unknown"
return attributes

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,
@ -9,7 +13,13 @@ from homeassistant.components.switch import (
DEVICE_ATTRIBUTES_HOST = [
"interface",
"source",
"authorized",
"bypassed",
"last-seen",
"signal-strength",
"tx-ccq",
"tx-rate",
"rx-rate",
]
@ -17,24 +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 = ""
data_is_on: str = "available"
data_name: str = ""
data_uid: str = ""
data_reference: 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 | 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",
@ -47,5 +59,8 @@ SENSOR_TYPES = {
data_uid="mac-address",
data_reference="mac-address",
data_attributes_list=DEVICE_ATTRIBUTES_HOST,
func="MikrotikHostDeviceTracker",
),
}
)
SENSOR_SERVICES = {}

View file

@ -1,57 +1,25 @@
"""Diagnostics support for Mikrotik Router."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
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",
}
from .const import DOMAIN, TO_REDACT
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]
diag: dict[str, Any] = {}
diag["entry"]: dict[str, Any] = {}
data_coordinator = hass.data[DOMAIN][config_entry.entry_id].data_coordinator
tracker_coordinator = hass.data[DOMAIN][config_entry.entry_id].data_coordinator
diag["entry"]["data"] = async_redact_data(config_entry.data, TO_REDACT)
diag["entry"]["options"] = async_redact_data(config_entry.options, TO_REDACT)
diag["data"] = async_redact_data(controller.data, TO_REDACT)
return diag
return {
"entry": {
"data": async_redact_data(config_entry.data, TO_REDACT),
"options": async_redact_data(config_entry.options, 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

@ -5,15 +5,9 @@
# format_attribute
# ---------------------------
def format_attribute(attr):
res = attr.replace("-", " ")
res = res.capitalize()
res = res.replace(" ip ", " IP ")
res = res.replace(" mac ", " MAC ")
res = res.replace(" mtu", " MTU")
res = res.replace("Sfp", "SFP")
res = res.replace("Poe", "POE")
res = res.replace(" tx", " TX")
res = res.replace(" rx", " RX")
res = attr.replace("-", "_")
res = res.replace(" ", "_")
res = res.lower()
return res

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

@ -4,16 +4,13 @@ import logging
import ssl
from time import time
from threading import Lock
from voluptuous import Optional
from .const import (
DEFAULT_LOGIN_METHOD,
DEFAULT_ENCODING,
)
import librouteros
from socket import error as socket_error, timeout as socket_timeout
_LOGGER = logging.getLogger(__name__)
@ -31,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
@ -47,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:
@ -121,47 +121,28 @@ 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
)
except (
librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ConnectionClosed,
librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError,
socket_timeout,
socket_error,
ssl.SSLError,
BrokenPipeError,
OSError,
) as api_error:
except Exception as e:
if not self.connection_error_reported:
_LOGGER.error(
"Mikrotik %s error while connecting: %s", self._host, api_error
)
self.connection_error_reported = True
self.error_to_strings("%s" % api_error)
self._connection = None
self.lock.release()
return False
except:
if not self.connection_error_reported:
_LOGGER.error(
"Mikrotik %s error while connecting: %s", self._host, "Unknown"
)
_LOGGER.error("Mikrotik %s error while connecting: %s", self._host, e)
self.connection_error_reported = True
self.error_to_strings(f"{e}")
self._connection = None
self.lock.release()
return False
@ -190,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
# ---------------------------
@ -198,11 +182,16 @@ class MikrotikAPI:
return self._connected
# ---------------------------
# path
# query
# ---------------------------
def path(self, path, return_list=True) -> Optional(list):
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 = {}
if not self.connection_check():
return None
@ -211,56 +200,47 @@ class MikrotikAPI:
try:
_LOGGER.debug("API query: %s", path)
response = self._connection.path(path)
except librouteros.exceptions.ConnectionClosed:
self.disconnect()
self.lock.release()
return None
except (
librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError,
socket_timeout,
socket_error,
ssl.SSLError,
BrokenPipeError,
OSError,
ValueError,
) as api_error:
self.disconnect("path", api_error)
self.lock.release()
return None
except:
self.disconnect("path")
except Exception as e:
self.disconnect("path", e)
self.lock.release()
return None
if return_list:
if response and return_list and not command:
try:
response = list(response)
except librouteros.exceptions.ConnectionClosed as api_error:
self.disconnect("building list for path", api_error)
except Exception as 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
except:
self.disconnect("building list for path")
elif response and command:
_LOGGER.debug("API query: %s, %s, %s", path, command, args)
try:
response = list(response(command, **args))
except Exception as e:
self.disconnect("path", e)
self.lock.release()
return None
self.lock.release()
return response if response else None
return response or None
# ---------------------------
# update
# set_value
# ---------------------------
def update(self, path, param, value, mod_param, mod_value) -> bool:
def set_value(self, path, param, value, mod_param, mod_value) -> bool:
"""Modify a parameter"""
entry_found = None
if not self.connection_check():
return False
response = self.path(path, return_list=False)
response = self.query(path, return_list=False)
if response is None:
return False
@ -275,7 +255,7 @@ class MikrotikAPI:
if not entry_found:
_LOGGER.error(
"Mikrotik %s Update parameter %s with value %s not found",
"Mikrotik %s set_value parameter %s with value %s not found",
self._host,
param,
value,
@ -286,27 +266,8 @@ class MikrotikAPI:
self.lock.acquire()
try:
response.update(**params)
except librouteros.exceptions.ConnectionClosed:
self.disconnect()
self.lock.release()
return False
except (
librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError,
socket_timeout,
socket_error,
ssl.SSLError,
BrokenPipeError,
OSError,
ValueError,
) as api_error:
self.disconnect("update", api_error)
self.lock.release()
return False
except:
self.disconnect("update")
except Exception as e:
self.disconnect("set_value", e)
self.lock.release()
return False
@ -316,61 +277,48 @@ 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 = {}
if not self.connection_check():
return False
response = self.path(path, return_list=False)
response = self.query(path, return_list=False)
if response is None:
return False
for tmp in response:
if param not in tmp:
continue
if param:
for tmp in response:
if param not in tmp:
continue
if tmp[param] != value:
continue
if tmp[param] != value:
continue
entry_found = tmp[".id"]
entry_found = tmp[".id"]
if not entry_found:
_LOGGER.error(
"Mikrotik %s Execute %s parameter %s with value %s not found",
self._host,
command,
param,
value,
)
return True
if not entry_found:
_LOGGER.error(
"Mikrotik %s Execute %s parameter %s with value %s not found",
self._host,
command,
param,
value,
)
return True
params = {".id": entry_found}
if attributes:
params.update(attributes)
params = {".id": entry_found}
self.lock.acquire()
try:
tuple(response(command, **params))
except librouteros.exceptions.ConnectionClosed:
self.disconnect()
self.lock.release()
return False
except (
librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError,
socket_timeout,
socket_error,
ssl.SSLError,
BrokenPipeError,
OSError,
ValueError,
) as api_error:
self.disconnect("execute", api_error)
self.lock.release()
return False
except:
self.disconnect("execute")
except Exception as e:
self.disconnect("execute", e)
self.lock.release()
return False
@ -386,7 +334,7 @@ class MikrotikAPI:
if not self.connection_check():
return False
response = self.path("/system/script", return_list=False)
response = self.query("/system/script", return_list=False)
if response is None:
return False
@ -407,88 +355,14 @@ class MikrotikAPI:
try:
run = response("run", **{".id": entry_found})
tuple(run)
except librouteros.exceptions.ConnectionClosed:
self.disconnect()
self.lock.release()
return False
except (
librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError,
socket_timeout,
socket_error,
ssl.SSLError,
BrokenPipeError,
OSError,
ValueError,
) as api_error:
self.disconnect("run_script", api_error)
self.lock.release()
return False
except:
self.disconnect("run_script")
except Exception as e:
self.disconnect("run_script", e)
self.lock.release()
return False
self.lock.release()
return True
# ---------------------------
# get_sfp
# ---------------------------
def get_sfp(self, interfaces) -> Optional(list):
"""Get sfp info"""
if not self.connection_check():
return None
response = self.path("/interface/ethernet", return_list=False)
if response is None:
return None
args = {".id": interfaces, "once": True}
self.lock.acquire()
try:
_LOGGER.debug("API query: %s %s", "/interface/ethernet/monitor", interfaces)
sfpinfo = response("monitor", **args)
except librouteros.exceptions.ConnectionClosed:
self.disconnect()
self.lock.release()
return None
except (
librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError,
ssl.SSLError,
socket_timeout,
socket_error,
BrokenPipeError,
OSError,
ValueError,
) as api_error:
self.disconnect("get_sfp", api_error)
self.lock.release()
return None
except:
self.disconnect("get_sfp")
self.lock.release()
return None
try:
sfpinfo = list(sfpinfo)
except librouteros.exceptions.ConnectionClosed as api_error:
self.disconnect("get_sfp", api_error)
self.lock.release()
return None
except:
self.disconnect("get_sfp")
self.lock.release()
return None
self.lock.release()
return sfpinfo if sfpinfo else None
# ---------------------------
# arp_ping
# ---------------------------
@ -497,7 +371,7 @@ class MikrotikAPI:
if not self.connection_check():
return False
response = self.path("/ping", return_list=False)
response = self.query("/ping", return_list=False)
if response is None:
return False
@ -512,39 +386,15 @@ class MikrotikAPI:
try:
# _LOGGER.debug("Ping host query: %s", args["address"])
ping = response("/ping", **args)
except librouteros.exceptions.ConnectionClosed:
self.disconnect()
self.lock.release()
return False
except (
librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError,
socket_timeout,
socket_error,
ssl.SSLError,
BrokenPipeError,
OSError,
ValueError,
) as api_error:
self.disconnect("arp_ping", api_error)
self.lock.release()
return False
except:
self.disconnect("arp_ping")
except Exception as e:
self.disconnect("arp_ping", e)
self.lock.release()
return False
try:
ping = list(ping)
except librouteros.exceptions.ConnectionClosed as api_error:
self.disconnect("arp_ping", api_error)
self.lock.release()
return False
except:
self.disconnect("arp_ping")
except Exception as e:
self.disconnect("arp_ping", e)
self.lock.release()
return False
@ -570,7 +420,7 @@ class MikrotikAPI:
if not self.connection_check():
return False, False
response = self.path("/ip/accounting")
response = self.query("/ip/accounting")
if response is None:
return False, False
@ -598,44 +448,21 @@ class MikrotikAPI:
return 0
if use_accounting:
accounting = self.path("/ip/accounting", return_list=False)
accounting = self.query("/ip/accounting", return_list=False)
self.lock.acquire()
try:
# Prepare command
take = accounting("snapshot/take")
except librouteros.exceptions.ConnectionClosed:
self.disconnect()
self.lock.release()
return 0
except (
librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ProtocolError,
librouteros.exceptions.FatalError,
socket_timeout,
socket_error,
ssl.SSLError,
BrokenPipeError,
OSError,
ValueError,
) as api_error:
self.disconnect("accounting_snapshot", api_error)
self.lock.release()
return 0
except:
self.disconnect("accounting_snapshot")
except Exception as e:
self.disconnect("accounting_snapshot", e)
self.lock.release()
return 0
try:
list(take)
except librouteros.exceptions.ConnectionClosed as api_error:
self.disconnect("accounting_snapshot", api_error)
self.lock.release()
return 0
except:
self.disconnect("accounting_snapshot")
except Exception as e:
self.disconnect("accounting_snapshot", e)
self.lock.release()
return 0

View file

@ -1,307 +1,90 @@
"""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 homeassistant.const import (
CONF_NAME,
CONF_HOST,
ATTR_ATTRIBUTION,
)
from homeassistant.helpers.entity import DeviceInfo
from datetime import date, datetime
from decimal import Decimal
from typing import Any
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
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 .const import (
CONF_SENSOR_PORT_TRAFFIC,
DEFAULT_SENSOR_PORT_TRAFFIC,
DOMAIN,
ATTRIBUTION,
)
from .sensor_types import (
MikrotikSensorEntityDescription,
SENSOR_TYPES,
SENSOR_SERVICES,
DEVICE_ATTRIBUTES_IFACE_ETHER,
DEVICE_ATTRIBUTES_IFACE_SFP,
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):
"""Set up device tracker for Mikrotik Router component."""
inst = config_entry.data[CONF_NAME]
mikrotik_controller = hass.data[DOMAIN][config_entry.entry_id]
sensors = {}
@callback
def update_controller():
"""Update the values of the controller."""
update_items(
inst, config_entry, mikrotik_controller, async_add_entities, sensors
)
mikrotik_controller.listeners.append(
async_dispatcher_connect(
hass, mikrotik_controller.signal_update, update_controller
)
)
update_controller()
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 async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
# update_items
# MikrotikSensor
# ---------------------------
@callback
def update_items(inst, config_entry, mikrotik_controller, async_add_entities, sensors):
"""Update sensor state from the controller."""
new_sensors = []
for sensor, sid_func in zip(
# Sensor type name
[
"environment",
"traffic_rx",
"traffic_tx",
"client_traffic_rx",
"client_traffic_tx",
"client_traffic_lan_rx",
"client_traffic_lan_tx",
"client_traffic_wan_rx",
"client_traffic_wan_tx",
],
# Entity function
[
MikrotikControllerSensor,
MikrotikInterfaceTrafficSensor,
MikrotikInterfaceTrafficSensor,
MikrotikClientTrafficSensor,
MikrotikClientTrafficSensor,
MikrotikClientTrafficSensor,
MikrotikClientTrafficSensor,
MikrotikClientTrafficSensor,
MikrotikClientTrafficSensor,
],
):
if sensor.startswith("traffic_") and not config_entry.options.get(
CONF_SENSOR_PORT_TRAFFIC, DEFAULT_SENSOR_PORT_TRAFFIC
):
continue
uid_sensor = SENSOR_TYPES[sensor]
for uid in mikrotik_controller.data[uid_sensor.data_path]:
uid_data = mikrotik_controller.data[uid_sensor.data_path]
if (
uid_sensor.data_path == "interface"
and uid_data[uid]["type"] == "bridge"
):
continue
if (
uid_sensor.data_path == "client_traffic"
and uid_sensor.data_attribute not in uid_data[uid].keys()
):
continue
item_id = f"{inst}-{sensor}-{uid_data[uid][uid_sensor.data_reference]}"
_LOGGER.debug("Updating sensor %s", item_id)
if item_id in sensors:
if sensors[item_id].enabled:
sensors[item_id].async_schedule_update_ha_state()
continue
sensors[item_id] = sid_func(
inst=inst,
uid=uid,
mikrotik_controller=mikrotik_controller,
entity_description=uid_sensor,
)
new_sensors.append(sensors[item_id])
for sensor in SENSOR_TYPES:
if sensor.startswith("system_"):
uid_sensor = SENSOR_TYPES[sensor]
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}"
_LOGGER.debug("Updating sensor %s", item_id)
if item_id in sensors:
if sensors[item_id].enabled:
sensors[item_id].async_schedule_update_ha_state()
continue
sensors[item_id] = MikrotikControllerSensor(
inst=inst,
uid="",
mikrotik_controller=mikrotik_controller,
entity_description=uid_sensor,
)
new_sensors.append(sensors[item_id])
if new_sensors:
async_add_entities(new_sensors, True)
# ---------------------------
# MikrotikControllerSensor
# ---------------------------
class MikrotikControllerSensor(SensorEntity):
"""Define an Mikrotik Controller sensor."""
class MikrotikSensor(MikrotikEntity, SensorEntity):
"""Define an Mikrotik sensor."""
def __init__(
self,
inst,
uid: "",
mikrotik_controller,
entity_description: MikrotikSensorEntityDescription,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
"""Initialize."""
self.entity_description = entity_description
self._inst = inst
self._ctrl = mikrotik_controller
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]
super().__init__(coordinator, entity_description, uid)
self._attr_suggested_unit_of_measurement = (
self.entity_description.suggested_unit_of_measurement
)
@property
def name(self) -> str:
"""Return the name."""
if self._uid:
if self.entity_description.name:
return f"{self._inst} {self._data[self.entity_description.data_name]} {self.entity_description.name}"
return f"{self._inst} {self._data[self.entity_description.data_name]}"
else:
return f"{self._inst} {self.entity_description.name}"
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 unique_id(self) -> str:
"""Return a unique id for this entity."""
if self._uid:
return f"{self._inst.lower()}-{self.entity_description.key}-{self._data[self.entity_description.data_reference].lower()}"
else:
return f"{self._inst.lower()}-{self.entity_description.key}"
@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"
@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
return None
@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}",
model=f"{self._ctrl.data['resource']['board-name']}",
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}",
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):
"""Run when entity about to be added to hass."""
_LOGGER.debug("New sensor %s (%s)", self._inst, self.unique_id)
# ---------------------------
# MikrotikInterfaceTrafficSensor
# ---------------------------
class MikrotikInterfaceTrafficSensor(MikrotikControllerSensor):
class MikrotikInterfaceTrafficSensor(MikrotikSensor):
"""Define an Mikrotik MikrotikInterfaceTrafficSensor sensor."""
@property
@ -319,30 +102,35 @@ class MikrotikInterfaceTrafficSensor(MikrotikControllerSensor):
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
elif self._data["type"] == "wlan":
for variable in DEVICE_ATTRIBUTES_IFACE_WIRELESS:
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
return attributes
# ---------------------------
# MikrotikClientTrafficSensor
# ---------------------------
class MikrotikClientTrafficSensor(MikrotikControllerSensor):
class MikrotikClientTrafficSensor(MikrotikSensor):
"""Define an Mikrotik MikrotikClientTrafficSensor sensor."""
@property
def name(self) -> str:
"""Return the name."""
return f"{self._data[self.entity_description.data_name]} {self.entity_description.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,11 +13,16 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
)
from homeassistant.const import (
TEMP_CELSIUS,
ELECTRIC_POTENTIAL_VOLT,
POWER_WATT,
PERCENTAGE,
REVOLUTIONS_PER_MINUTE,
UnitOfTemperature,
UnitOfDataRate,
UnitOfInformation,
UnitOfElectricPotential,
UnitOfElectricCurrent,
UnitOfPower,
)
from .const import DOMAIN
DEVICE_ATTRIBUTES_IFACE = [
@ -63,33 +72,79 @@ DEVICE_ATTRIBUTES_IFACE_SFP = [
"eeprom-checksum",
]
DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = ["address", "mac-address", "host-name"]
DEVICE_ATTRIBUTES_IFACE_WIRELESS = [
"ssid",
"mode",
"radio-name",
"interface-type",
"country",
"installation",
"antenna-gain",
"frequency",
"band",
"channel-width",
"secondary-frequency",
"wireless-protocol",
"rate-set",
"distance",
"tx-power-mode",
"vlan-id",
"wds-mode",
"wds-default-bridge",
"bridge-mode",
"hide-ssid",
]
DEVICE_ATTRIBUTES_CLIENT_TRAFFIC = [
"address",
"mac-address",
"host-name",
"authorized",
"bypassed",
]
DEVICE_ATTRIBUTES_GPS = [
"valid",
"latitude",
"longitude",
"altitude",
"speed",
"destination-bearing",
"true-bearing",
"magnetic-bearing",
"satellites",
"fix-quality",
"horizontal-dilution",
]
@dataclass
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 = ""
data_uid: str = ""
data_reference: 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 | 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",
@ -97,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,
@ -112,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",
@ -127,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",
@ -142,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",
@ -157,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=None,
entity_category=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="fan1-speed",
@ -172,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=None,
entity_category=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="health",
data_attribute="fan2-speed",
@ -187,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,
@ -202,13 +431,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_cpu-load": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_cpu-load",
name="CPU load",
icon="mdi:speedometer",
native_unit_of_measurement=PERCENTAGE,
device_class=None,
state_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="System",
data_path="resource",
@ -217,13 +446,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_memory-usage": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_memory-usage",
name="Memory usage",
icon="mdi:memory",
native_unit_of_measurement=PERCENTAGE,
device_class=None,
state_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="System",
data_path="resource",
@ -232,13 +461,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_hdd-usage": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_hdd-usage",
name="HDD usage",
icon="mdi:harddisk",
native_unit_of_measurement=PERCENTAGE,
device_class=None,
state_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="System",
data_path="resource",
@ -247,13 +476,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_clients-wired": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_clients-wired",
name="Wired Clients",
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",
@ -262,13 +491,13 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"system_clients-wireless": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_clients-wireless",
name="Wireless Clients",
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",
@ -277,12 +506,61 @@ SENSOR_TYPES = {
data_uid="",
data_reference="",
),
"traffic_tx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="system_captive-authorized",
name="Captive portal clients",
icon="mdi:key-wireless",
native_unit_of_measurement=None,
device_class=None,
state_class=SensorStateClass.MEASUREMENT,
entity_category=None,
ha_group="System",
data_path="resource",
data_attribute="captive_authorized",
data_name="",
data_uid="",
data_reference="",
),
MikrotikSensorEntityDescription(
key="system_gps-latitude",
name="Latitude",
icon="mdi:latitude",
native_unit_of_measurement=None,
device_class=None,
state_class=None,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="gps",
data_attribute="latitude",
data_name="",
data_uid="",
data_reference="",
data_attributes_list=DEVICE_ATTRIBUTES_GPS,
),
MikrotikSensorEntityDescription(
key="system_gps-longitude",
name="Longitude",
icon="mdi:longitude",
native_unit_of_measurement=None,
device_class=None,
state_class=None,
entity_category=EntityCategory.DIAGNOSTIC,
ha_group="System",
data_path="gps",
data_attribute="longitude",
data_name="",
data_uid="",
data_reference="",
data_attributes_list=DEVICE_ATTRIBUTES_GPS,
),
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",
@ -290,17 +568,20 @@ SENSOR_TYPES = {
ha_connection_value="data__port-mac-address",
data_path="interface",
data_attribute="tx",
data_name="name",
data_name="default-name",
data_uid="",
data_reference="default-name",
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",
@ -308,17 +589,62 @@ SENSOR_TYPES = {
ha_connection_value="data__port-mac-address",
data_path="interface",
data_attribute="rx",
data_name="name",
data_name="default-name",
data_uid="",
data_reference="default-name",
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikInterfaceTrafficSensor",
),
"client_traffic_lan_tx": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="tx-total",
name="TX total",
icon="mdi:upload-network",
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",
ha_connection=CONNECTION_NETWORK_MAC,
ha_connection_value="data__port-mac-address",
data_path="interface",
data_attribute="tx-current",
data_name="default-name",
data_uid="",
data_reference="default-name",
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikInterfaceTrafficSensor",
),
MikrotikSensorEntityDescription(
key="rx-total",
name="RX total",
icon="mdi:download-network",
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",
ha_connection=CONNECTION_NETWORK_MAC,
ha_connection_value="data__port-mac-address",
data_path="interface",
data_attribute="rx-current",
data_name="default-name",
data_uid="",
data_reference="default-name",
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikInterfaceTrafficSensor",
),
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="",
@ -330,13 +656,16 @@ SENSOR_TYPES = {
data_uid="",
data_reference="mac-address",
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="",
@ -348,13 +677,16 @@ SENSOR_TYPES = {
data_uid="",
data_reference="mac-address",
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="",
@ -366,13 +698,16 @@ SENSOR_TYPES = {
data_uid="",
data_reference="mac-address",
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="",
@ -384,13 +719,16 @@ SENSOR_TYPES = {
data_uid="",
data_reference="mac-address",
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="",
@ -402,13 +740,16 @@ SENSOR_TYPES = {
data_uid="",
data_reference="mac-address",
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="",
@ -420,8 +761,9 @@ SENSOR_TYPES = {
data_uid="",
data_reference="mac-address",
data_attributes_list=DEVICE_ATTRIBUTES_CLIENT_TRAFFIC,
func="MikrotikClientTrafficSensor",
),
"environment": MikrotikSensorEntityDescription(
MikrotikSensorEntityDescription(
key="environment",
name="",
icon="mdi:clipboard-list",
@ -438,4 +780,6 @@ SENSOR_TYPES = {
data_uid="name",
data_reference="name",
),
}
)
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

@ -1,25 +1,27 @@
{
"config": {
"error": {
"cannot_connect": "Cannot connect to Mikrotik.",
"connection_timeout": "Mikrotik connection timeout.",
"name_exists": "Name already exists.",
"ssl_handshake_failure": "SSL handshake failure",
"wrong_login": "Invalid user name or password."
},
"step": {
"user": {
"data": {
"host": "Host",
"name": "Name of the integration",
"password": "Password",
"port": "Port",
"ssl": "Use SSL",
"username": "Username"
},
"title": "Set up Mikrotik Router",
"description": "Set up Mikrotik Router integration.",
"title": "Mikrotik Router"
"data": {
"name": "Name of the integration",
"host": "Host",
"port": "Port",
"username": "Username",
"password": "Password",
"ssl": "Use SSL",
"verify_ssl": "Verify SSL"
}
}
},
"error": {
"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."
}
},
"options": {
@ -32,24 +34,28 @@
"track_network_hosts_timeout": "Track network devices timeout (seconds)",
"zone": "Zone for device tracker"
},
"title": "Basic options"
"title": "Mikrotik Router options (1/2)",
"description": "Configure integration"
},
"sensor_select": {
"data": {
"sensor_port_tracker": "Port tracker sensors",
"sensor_port_traffic": "Port traffic sensors",
"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",
"sensor_simple_queues": "Simple queues switches",
"sensor_nat": "NAT switches",
"sensor_mangle": "Mangle switches",
"sensor_filter": "Filter switches",
"sensor_scripts": "Script switches",
"sensor_environment": "Environment variable sensors",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_scripts": "Scripts switches",
"sensor_environment": "Environment variable sensors"
"sensor_filter": "Filter switches"
},
"title": "Sensor selection"
"title": "Mikrotik Router options (2/2)",
"description": "Enable sensors and switches"
}
}
}

View file

@ -1,166 +1,70 @@
"""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.const import CONF_NAME, CONF_HOST, ATTR_ATTRIBUTION
from homeassistant.core import callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
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 .const import DOMAIN, ATTRIBUTION
from .switch_types import (
MikrotikSwitchEntityDescription,
SWITCH_TYPES,
SENSOR_TYPES,
SENSOR_SERVICES,
DEVICE_ATTRIBUTES_IFACE_ETHER,
DEVICE_ATTRIBUTES_IFACE_SFP,
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):
"""Set up switches for Mikrotik Router component."""
inst = config_entry.data[CONF_NAME]
mikrotik_controller = hass.data[DOMAIN][config_entry.entry_id]
switches = {}
@callback
def update_controller():
"""Update the values of the controller."""
update_items(inst, mikrotik_controller, async_add_entities, switches)
mikrotik_controller.listeners.append(
async_dispatcher_connect(
hass, mikrotik_controller.signal_update, update_controller
)
)
update_controller()
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
_async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up entry for component"""
dispatcher = {
"MikrotikSwitch": MikrotikSwitch,
"MikrotikPortSwitch": MikrotikPortSwitch,
"MikrotikNATSwitch": MikrotikNATSwitch,
"MikrotikMangleSwitch": MikrotikMangleSwitch,
"MikrotikFilterSwitch": MikrotikFilterSwitch,
"MikrotikQueueSwitch": MikrotikQueueSwitch,
"MikrotikKidcontrolPauseSwitch": MikrotikKidcontrolPauseSwitch,
}
await async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
# update_items
# MikrotikSwitch
# ---------------------------
@callback
def update_items(inst, mikrotik_controller, async_add_entities, switches):
"""Update device switch state from the controller."""
new_switches = []
# Add switches
for switch, sid_func in zip(
# Switch type name
[
"interface",
"nat",
"mangle",
"filter",
"ppp_secret",
"queue",
"kidcontrol_enable",
"kidcontrol_pause",
],
# Entity function
[
MikrotikControllerPortSwitch,
MikrotikControllerNATSwitch,
MikrotikControllerMangleSwitch,
MikrotikControllerFilterSwitch,
MikrotikControllerSwitch,
MikrotikControllerQueueSwitch,
MikrotikControllerSwitch,
MikrotikControllerKidcontrolPauseSwitch,
],
):
uid_switch = SWITCH_TYPES[switch]
for uid in mikrotik_controller.data[SWITCH_TYPES[switch].data_path]:
uid_data = mikrotik_controller.data[SWITCH_TYPES[switch].data_path]
item_id = f"{inst}-{switch}-{uid_data[uid][uid_switch.data_reference]}"
_LOGGER.debug("Updating sensor %s", item_id)
if item_id in switches:
if switches[item_id].enabled:
switches[item_id].async_schedule_update_ha_state()
continue
switches[item_id] = sid_func(
inst=inst,
uid=uid,
mikrotik_controller=mikrotik_controller,
entity_description=uid_switch,
)
new_switches.append(switches[item_id])
if new_switches:
async_add_entities(new_switches)
# ---------------------------
# MikrotikControllerSwitch
# ---------------------------
class MikrotikControllerSwitch(SwitchEntity, RestoreEntity):
class MikrotikSwitch(MikrotikEntity, SwitchEntity, RestoreEntity):
"""Representation of a switch."""
def __init__(
self,
inst,
uid,
mikrotik_controller,
entity_description: MikrotikSwitchEntityDescription,
):
self.entity_description = entity_description
self._inst = inst
self._ctrl = mikrotik_controller
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._data = mikrotik_controller.data[self.entity_description.data_path][uid]
@property
def available(self) -> bool:
"""Return if controller is available."""
return self._ctrl.connected()
@property
def name(self) -> str:
"""Return the name."""
if self.entity_description.data_name_comment and self._data["comment"]:
return (
f"{self._inst} {self.entity_description.name} {self._data['comment']}"
)
return f"{self._inst} {self.entity_description.name} {self._data[self.entity_description.data_name]}"
@property
def unique_id(self) -> str:
"""Return a unique id for this entity."""
return f"{self._inst.lower()}-{self.entity_description.key}-{self._data[self.entity_description.data_reference].lower()}"
@property
def is_on(self) -> bool:
"""Return true if device is on."""
return self._data[self.entity_description.data_is_on]
return self._data[self.entity_description.data_attribute]
@property
def icon(self) -> str:
"""Return the icon."""
if self._data[self.entity_description.data_is_on]:
if self._data[self.entity_description.data_attribute]:
return self.entity_description.icon_enabled
else:
return self.entity_description.icon_disabled
@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
def turn_on(self, **kwargs: Any) -> None:
"""Required abstract method."""
pass
@ -171,65 +75,33 @@ class MikrotikControllerSwitch(SwitchEntity, RestoreEntity):
async def async_turn_on(self) -> None:
"""Turn on the switch."""
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.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()
@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.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}",
model=f"{self._ctrl.data['resource']['board-name']}",
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']}"),
)
return info
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
_LOGGER.debug("New switch %s (%s)", self._inst, self.unique_id)
self.coordinator.set_value(path, param, value, mod_param, True)
await self.coordinator.async_refresh()
# ---------------------------
# MikrotikControllerPortSwitch
# MikrotikPortSwitch
# ---------------------------
class MikrotikControllerPortSwitch(MikrotikControllerSwitch):
class MikrotikPortSwitch(MikrotikSwitch):
"""Representation of a network port switch."""
@property
@ -247,6 +119,11 @@ class MikrotikControllerPortSwitch(MikrotikControllerSwitch):
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
elif self._data["type"] == "wlan":
for variable in DEVICE_ATTRIBUTES_IFACE_WIRELESS:
if variable in self._data:
attributes[format_attribute(variable)] = self._data[variable]
return attributes
@property
@ -264,6 +141,9 @@ class MikrotikControllerPortSwitch(MikrotikControllerSwitch):
async def async_turn_on(self) -> Optional[str]:
"""Turn on the switch."""
if "write" not in self.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = self.entity_description.data_reference
if self._data["about"] == "managed by CAPsMAN":
@ -273,16 +153,19 @@ class MikrotikControllerPortSwitch(MikrotikControllerSwitch):
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.coordinator.data["access"]:
return
path = self.entity_description.data_switch_path
param = self.entity_description.data_reference
if self._data["about"] == "managed by CAPsMAN":
@ -292,201 +175,223 @@ class MikrotikControllerPortSwitch(MikrotikControllerSwitch):
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()
# ---------------------------
# MikrotikControllerNATSwitch
# MikrotikNATSwitch
# ---------------------------
class MikrotikControllerNATSwitch(MikrotikControllerSwitch):
class MikrotikNATSwitch(MikrotikSwitch):
"""Representation of a NAT switch."""
@property
def name(self) -> str:
"""Return the name."""
if self._data["comment"]:
return f"{self._inst} NAT {self._data['comment']}"
return f"{self._inst} NAT {self._data['name']}"
async def async_turn_on(self) -> None:
"""Turn on the switch."""
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.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()
# ---------------------------
# MikrotikControllerMangleSwitch
# MikrotikMangleSwitch
# ---------------------------
class MikrotikControllerMangleSwitch(MikrotikControllerSwitch):
class MikrotikMangleSwitch(MikrotikSwitch):
"""Representation of a Mangle switch."""
async def async_turn_on(self) -> None:
"""Turn on the switch."""
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.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()
# ---------------------------
# MikrotikControllerFilterSwitch
# MikrotikFilterSwitch
# ---------------------------
class MikrotikControllerFilterSwitch(MikrotikControllerSwitch):
class MikrotikFilterSwitch(MikrotikSwitch):
"""Representation of a Filter switch."""
async def async_turn_on(self) -> None:
"""Turn on the switch."""
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.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()
# ---------------------------
# MikrotikControllerQueueSwitch
# MikrotikQueueSwitch
# ---------------------------
class MikrotikControllerQueueSwitch(MikrotikControllerSwitch):
class MikrotikQueueSwitch(MikrotikSwitch):
"""Representation of a queue switch."""
async def async_turn_on(self) -> None:
"""Turn on the switch."""
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.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()
# ---------------------------
# MikrotikControllerKidcontrolPauseSwitch
# MikrotikKidcontrolPauseSwitch
# ---------------------------
class MikrotikControllerKidcontrolPauseSwitch(MikrotikControllerSwitch):
class MikrotikKidcontrolPauseSwitch(MikrotikSwitch):
"""Representation of a queue switch."""
async def async_turn_on(self) -> None:
"""Turn on the switch."""
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.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,8 +1,10 @@
"""Definitions for Mikrotik Router sensor entities."""
"""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
from homeassistant.helpers.entity import EntityCategory
from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntityDescription,
@ -57,6 +59,29 @@ DEVICE_ATTRIBUTES_IFACE_SFP = [
"eeprom-checksum",
]
DEVICE_ATTRIBUTES_IFACE_WIRELESS = [
"ssid",
"mode",
"radio-name",
"interface-type",
"country",
"installation",
"antenna-gain",
"frequency",
"band",
"channel-width",
"secondary-frequency",
"wireless-protocol",
"rate-set",
"distance",
"tx-power-mode",
"vlan-id",
"wds-mode",
"wds-default-bridge",
"bridge-mode",
"hide-ssid",
]
DEVICE_ATTRIBUTES_NAT = [
"protocol",
"dst-port",
@ -105,6 +130,7 @@ DEVICE_ATTRIBUTES_PPP_SECRET = [
]
DEVICE_ATTRIBUTES_KIDCONTROL = [
"blocked",
"rate-limit",
"mon",
"tue",
@ -141,26 +167,27 @@ 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 = ""
data_is_on: str = "enabled"
data_switch_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 | 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"
SWITCH_TYPES = {
"interface": MikrotikSwitchEntityDescription(
SENSOR_TYPES: tuple[MikrotikSwitchEntityDescription, ...] = (
MikrotikSwitchEntityDescription(
key="interface",
name="port",
name="Port",
icon_enabled="mdi:lan-connect",
icon_disabled="mdi:lan-pending",
entity_category=None,
@ -169,14 +196,15 @@ SWITCH_TYPES = {
ha_connection_value="data__port-mac-address",
data_path="interface",
data_switch_path="/interface",
data_name="name",
data_name="default-name",
data_uid="name",
data_reference="default-name",
data_attributes_list=DEVICE_ATTRIBUTES_IFACE,
func="MikrotikPortSwitch",
),
"nat": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="nat",
name="NAT",
name="",
icon_enabled="mdi:network-outline",
icon_disabled="mdi:network-off-outline",
entity_category=None,
@ -190,10 +218,11 @@ SWITCH_TYPES = {
data_uid="uniq-id",
data_reference="uniq-id",
data_attributes_list=DEVICE_ATTRIBUTES_NAT,
func="MikrotikNATSwitch",
),
"mangle": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="mangle",
name="Mangle",
name="",
icon_enabled="mdi:bookmark-outline",
icon_disabled="mdi:bookmark-off-outline",
entity_category=None,
@ -207,10 +236,11 @@ SWITCH_TYPES = {
data_uid="uniq-id",
data_reference="uniq-id",
data_attributes_list=DEVICE_ATTRIBUTES_MANGLE,
func="MikrotikMangleSwitch",
),
"filter": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="filter",
name="Filter",
name="",
icon_enabled="mdi:filter-variant",
icon_disabled="mdi:filter-variant-remove",
entity_category=None,
@ -224,8 +254,9 @@ SWITCH_TYPES = {
data_uid="uniq-id",
data_reference="uniq-id",
data_attributes_list=DEVICE_ATTRIBUTES_FILTER,
func="MikrotikFilterSwitch",
),
"ppp_secret": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="ppp_secret",
name="PPP Secret",
icon_enabled="mdi:account-outline",
@ -241,9 +272,9 @@ SWITCH_TYPES = {
data_reference="name",
data_attributes_list=DEVICE_ATTRIBUTES_PPP_SECRET,
),
"queue": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="queue",
name="Queue",
name="",
icon_enabled="mdi:leaf",
icon_disabled="mdi:leaf-off",
entity_category=None,
@ -256,10 +287,11 @@ SWITCH_TYPES = {
data_uid="name",
data_reference="name",
data_attributes_list=DEVICE_ATTRIBUTES_QUEUE,
func="MikrotikQueueSwitch",
),
"kidcontrol_enable": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="kidcontrol_enable",
name="kidcontrol",
name="",
icon_enabled="mdi:account",
icon_disabled="mdi:account-off",
entity_category=None,
@ -273,9 +305,9 @@ SWITCH_TYPES = {
data_reference="name",
data_attributes_list=DEVICE_ATTRIBUTES_KIDCONTROL,
),
"kidcontrol_pause": MikrotikSwitchEntityDescription(
MikrotikSwitchEntityDescription(
key="kidcontrol_paused",
name="kidcontrol paused",
name="paused",
icon_enabled="mdi:account-outline",
icon_disabled="mdi:account-off-outline",
entity_category=None,
@ -283,11 +315,14 @@ SWITCH_TYPES = {
ha_connection=DOMAIN,
ha_connection_value="Kidcontrol",
data_path="kid-control",
data_is_on="paused",
data_attribute="paused",
data_switch_path="/ip/kid-control",
data_name="name",
data_uid="name",
data_reference="name",
data_attributes_list=DEVICE_ATTRIBUTES_KIDCONTROL,
func="MikrotikKidcontrolPauseSwitch",
),
}
)
SENSOR_SERVICES = {}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "ضبط جهاز Mikrotik Router",
"description": "إعداد دمج موجِّه (Mikrotik).",
"data": {
"name": "اسم الدّمج",
@ -11,7 +10,8 @@
"port": "منفذ",
"username": "اسم المستخدم",
"password": "كلمة المرور",
"ssl": "استعمل طبقة الوصلات الآمنة (SSL)"
"ssl": "استعمل طبقة الوصلات الآمنة (SSL)",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,7 +20,8 @@
"cannot_connect": "لا يمكن الاتصال بـ (Mikrotik).",
"ssl_handshake_failure": "فشل تأكيد الاتصال (SSL)",
"connection_timeout": "مهلة اتصال (Mikrotik).",
"wrong_login": "خطأ في اسم المستخدم أو كلمة مرور."
"wrong_login": "خطأ في اسم المستخدم أو كلمة مرور.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -31,9 +32,10 @@
"track_iface_clients": "إظهار عنوان التحكّم في الوسائط (MAC) و بروتوكول الإنترنت (IP) للعميل على الواجهات",
"unit_of_measurement": "وحدة قياس",
"track_network_hosts_timeout": "مهلة تتبّع أجهزة الشبكة (بالثواني)",
"zone": "Zone for device tracker"
"zone": "منطقة لتعقُّب الجهاز"
},
"title": "الخيارات الأساسية"
"title": "خيارات جهاز Mikrotik Router (1\/2)",
"description": "تهيئة التكامل"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "مُبدِّلات ترجمة عنوان الشبكة",
"sensor_scripts": "مُبدِّلات النصوص",
"sensor_environment": "مُستشعرات متغير البيئة",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "مراقبة الأطفال",
"sensor_mangle": "مفاتيح المِكواة الأُسطوانية",
"sensor_ppp": "مستخدمي PPP",
"sensor_filter": "مفاتيح الترشيح",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "حدد المُستشعرات"
"title": "خيارات جهاز Mikrotik Router (2\/2)",
"description": "تمكين أجهزة الاستشعار والمفاتيح"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Nastavit Mikrotik Router",
"description": "Nastavte integraci Mikrotik Router.",
"data": {
"name": "Název integrace",
@ -11,7 +10,8 @@
"port": "Port",
"username": "Uživatel",
"password": "Heslo",
"ssl": "Použití SSL"
"ssl": "Použití SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Zobrazit klientské MAC a IP na rozhraních",
"unit_of_measurement": "Jednotka měření",
"track_network_hosts_timeout": "Časový limit sledování síťových zařízení (v sekundách)",
"zone": "Zone for device tracker"
"zone": "Zóna pro sledování zařízení"
},
"title": "Základní možnosti"
"title": "Nastavení Mikrotik Router (1\/2)",
"description": "Konfigurovat integraci"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "Přepínače NAT",
"sensor_scripts": "Přepínače skriptů",
"sensor_environment": "Senzory prostředí",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Dětská kontrola",
"sensor_mangle": "Mangle spínače",
"sensor_ppp": "Uživatelé PPP",
"sensor_filter": "Přepínače filtrů",
"sensor_client_captive": "Captive portál data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Vyberte senzory"
"title": "Nastavení Mikrotik Router (2\/2)",
"description": "Povolit senzory a přepínače"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Mikrotik-Router einrichten",
"description": "Mikrotik-Router-Integration einstellen",
"data": {
"name": "Name der Integration",
@ -11,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": {
@ -31,27 +32,30 @@
"track_iface_clients": "Client MAC und IP auf dem Interface anzeigen",
"unit_of_measurement": "Maßeinheit",
"track_network_hosts_timeout": "Zeitlimit für Netzwerkgeräte verfolgen (Sekunden)",
"zone": "Zone for device tracker"
"zone": "Zone für Gerätetracker"
},
"title": "Grundlegende Optionen"
"title": "Mikrotik-Router-Optionen (1\/2)",
"description": "Integration konfigurieren"
},
"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": "Umweltvariable Sensoren",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_environment": "Umgebungsvariablen-Sensoren",
"sensor_kidcontrol": "Kindersicherung",
"sensor_mangle": "Mangle-Schalter",
"sensor_ppp": "PPP-Nutzer",
"sensor_filter": "Schalter für Filterregeln",
"sensor_client_captive": "Captive-Portal Daten",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Sensoren auswählen"
"title": "Mikrotik-Router-Optionen (2\/2)",
"description": "Sensoren und Schalter aktivieren"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Ρύθμιση του Mikrotik Router",
"description": "Ολοκλήρωση εγκατάστασης Mikrotik Router.",
"data": {
"name": "Όνομα ολοκλήρωσης",
@ -11,7 +10,8 @@
"port": "Θύρα",
"username": "Όνομα χρήστη",
"password": "Κωδικός πρόσβασης",
"ssl": "Χρήση SSL"
"ssl": "Χρήση SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,7 +20,8 @@
"cannot_connect": "Αδυναμία σύνδεσης στον Mikrotik.",
"ssl_handshake_failure": "Αποτυχίας χειραψίας SSL",
"connection_timeout": "Διακοπή σύνδεσης με Mikrotik.",
"wrong_login": "Το όνομα χρήστη και ο κωδικός πρόσβασης δεν είναι έγκυρα."
"wrong_login": "Το όνομα χρήστη και ο κωδικός πρόσβασης δεν είναι έγκυρα.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -31,9 +32,10 @@
"track_iface_clients": "Προβολή IP και MAC πελάτη στη διεπαφή",
"unit_of_measurement": "Μονάδα μέτρησης",
"track_network_hosts_timeout": "Εξωχρονισμός ανίχνευσης συσκευών διαδικτύου (δευτερόλεπτα)",
"zone": "Zone for device tracker"
"zone": "Ζώνη για παρακολούθηση συσκευής"
},
"title": "Βασικές επιογές"
"title": "Επιλογές Mikrotik Router (1\/2)",
"description": "Διαμόρφωση ολοκλήρωσης"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "Διακόπτες NAT",
"sensor_scripts": "Διακόπτες δέσμης ενεργειών",
"sensor_environment": "Αισθητήρες μεταβολών περιβάλλοντος",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Παιδικός έλεγχος",
"sensor_mangle": "Επιλογικοί διακόπτες",
"sensor_ppp": "Χρήστες PPP",
"sensor_filter": "Διακόπτες φίλτρου",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Επιλέξτε αισθητήρες"
"title": "Επιλογές Mikrotik Router (2\/2)",
"description": "Ενεργοποίηση αισθητήρων και διακοπτών"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Set up Mikrotik Router",
"description": "Set up Mikrotik Router integration.",
"data": {
"name": "Name of the integration",
@ -11,7 +10,8 @@
"port": "Port",
"username": "Username",
"password": "Password",
"ssl": "Use SSL"
"ssl": "Use SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -33,7 +34,8 @@
"track_network_hosts_timeout": "Track network devices timeout (seconds)",
"zone": "Zone for device tracker"
},
"title": "Basic options"
"title": "Mikrotik Router options (1\/2)",
"description": "Configure integration"
},
"sensor_select": {
"data": {
@ -43,15 +45,17 @@
"sensor_client_traffic": "Client traffic sensors",
"sensor_simple_queues": "Simple queues switches",
"sensor_nat": "NAT switches",
"sensor_scripts": "Scripts switches",
"sensor_scripts": "Script switches",
"sensor_environment": "Environment variable sensors",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_filter": "Filter switches",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Select sensors"
"title": "Mikrotik Router options (2\/2)",
"description": "Enable sensors and switches"
}
}
}

View file

@ -1,17 +1,17 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"description": "Configurar integración del Mikrotik Router.",
"title": "Configurar un Mikrotik Router",
"description": "Configura la integración del Mikrotik Router.",
"data": {
"name": "Nombre de la integración",
"host": "Servidor",
"port": "Puerto",
"username": "Nombre de usuario",
"password": "Contraseña",
"ssl": "Usar SSL"
"ssl": "Usar SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,38 +20,42 @@
"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": {
"step": {
"basic_options": {
"data": {
"scan_interval": "Intervalo de escaneo (requiere reinicio del HA)",
"scan_interval": "Intervalo de escaneo (requiere reinicio de HA)",
"track_iface_clients": "Mostrar MAC e IP del cliente en las interfaces",
"unit_of_measurement": "Unidad de medida",
"track_network_hosts_timeout": "Monitorizar tiempo hasta desconexión automática de dispositivos de red (segundos)",
"zone": "Zone for device tracker"
"zone": "Zona para seguimiento de dispositivo"
},
"title": "Opciones básicas"
"title": "Opciones del Mikrotik Router (1\/2)",
"description": "Configurar la integración"
},
"sensor_select": {
"data": {
"track_network_hosts": "Monitorizar dispositivos de red",
"sensor_port_tracker": "Sensores de seguimiento portuario",
"sensor_port_traffic": "Sensores de tráfico portuario",
"sensor_port_tracker": "Sensores de seguimiento de puertos",
"sensor_port_traffic": "Sensores de tráfico de puertos",
"sensor_client_traffic": "Sensores de tráfico de clientes",
"sensor_simple_queues": "Interruptores de cola sencillos",
"sensor_nat": "Interruptores NAT",
"sensor_scripts": "Interruptores de switch",
"sensor_scripts": "Interruptores de script",
"sensor_environment": "Sensores variables de entorno",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Control de niños",
"sensor_mangle": "Interruptor Mangle",
"sensor_ppp": "Usuarios PPP",
"sensor_filter": "Filtros de interruptores",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Seleccionar sensores"
"title": "Opciones del Mikrotik Router 2\/2)",
"description": "Activar sensores e interruptores"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Configurar un Mikrotik Router",
"description": "Configura la integración del Mikrotik Router.",
"data": {
"name": "Nombre de la integración",
@ -11,7 +10,8 @@
"port": "Puerto",
"username": "Nombre de usuario",
"password": "Contraseña",
"ssl": "Utilizar SSL"
"ssl": "Utilizar SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Mostrar MAC e IP de cliente en las interfaces",
"unit_of_measurement": "Unidad de medida",
"track_network_hosts_timeout": "Rastrear el tiempo de espera de los dispositivos de red (segundos)",
"zone": "Zone for device tracker"
"zone": "Zona para rastreador de dispositivos"
},
"title": "Opciones básicas"
"title": "Opciones del Mikrotik Router (1\/2)",
"description": "Configurar la integración"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "Interruptores NAT",
"sensor_scripts": "Interruptores de script",
"sensor_environment": "Sensores de variable de entorno",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Control parental",
"sensor_mangle": "Conmutadores de mangle",
"sensor_ppp": "Usuarios PPP",
"sensor_filter": "Conmutadores de filtro",
"sensor_client_captive": "Datos portal cautivo",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Seleccionar sensores"
"title": "Opciones del Mikrotik Router (2\/2)",
"description": "Activar sensores e interruptores"
}
}
}

View file

@ -1,17 +1,17 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"description": "Configurer l'intégration de routeur Mikrotik.",
"title": "Configurer Mikrotik Router",
"description": "Configurer l'intégration de Mikrotik Router.",
"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"
}
}
},
@ -20,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": {
@ -31,27 +32,30 @@
"track_iface_clients": "Afficher l'IP et le MAC du client sur les interfaces",
"unit_of_measurement": "Unité de mesure",
"track_network_hosts_timeout": "Suivre l'expiration des appareils du réseau (secondes)",
"zone": "Zone for device tracker"
"zone": "Zone pour le traceur d'appareil"
},
"title": "Options de base"
"title": "Options Mikrotik Router (1\/2)",
"description": "Configurer l'intégration"
},
"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_pppusers": "Utilisateurs PPP",
"sensor_kidcontrol": "Contrôle parental",
"sensor_mangle": "Commutateurs Mangle",
"sensor_mangle": "Règles de Mangle",
"sensor_ppp": "Utilisateurs PPP",
"sensor_filter": "Filter switches"
"sensor_filter": "Règles de filtrage",
"sensor_client_captive": "Données du portail captif",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Sélectionner les capteurs"
"title": "Options Mikrotik Router (2\/2)",
"description": "Activer les capteurs et les sélecteurs"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "मिकरोटिक राउटर सेट करें",
"description": "Mikrotik राउटर इन्टीग्रेशन सेट अप करें।",
"data": {
"name": "इन्टीग्रेशन का नाम",
@ -11,7 +10,8 @@
"port": "पोर्ट",
"username": "उपयोगकर्ता नाम",
"password": "पासवर्ड",
"ssl": "SSL का उपयोग करें"
"ssl": "SSL का उपयोग करें",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,7 +20,8 @@
"cannot_connect": "Mikrotik से कनेक्ट नही कर सकते।",
"ssl_handshake_failure": "SSL हैन्डशेक विफ़ल हो गया",
"connection_timeout": "Mikrotik कनेक्शन टाइम आउट हो गया।",
"wrong_login": "अमान्य उपयोगकर्ता नाम या पासवर्ड।"
"wrong_login": "अमान्य उपयोगकर्ता नाम या पासवर्ड।",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -31,9 +32,10 @@
"track_iface_clients": "ग्राहक का MAC और IP इन्टरफ़ेस पर दिखाएं",
"unit_of_measurement": "माप का युनीट",
"track_network_hosts_timeout": "नेटवर्क डिवाइस ट्रैक टाइम आउट (सेकन्ड)",
"zone": "Zone for device tracker"
"zone": "डिवाइस ट्रैकर के लिए ज़ोन"
},
"title": "बेसिक विकल्प"
"title": "Mikrotik Router के विकल्प (1\/2)",
"description": "इंटीग्रेशन को कॉन्फ़िगर करें"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "NAT स्विच",
"sensor_scripts": "स्क्रिप्ट स्विच",
"sensor_environment": "पारिस्थितिक वेरिएबल सेंसर",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "बच्चे के लिए कंट्रोल",
"sensor_mangle": "मैंगल स्विच",
"sensor_ppp": "PPP यूज़र",
"sensor_filter": "फ़िल्टर स्विच",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "सेंसर चुनें"
"title": "Mikrotik Router के विकल्प (2\/2)",
"description": "सेंसर और स्विच को एनेबल करें"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Állítsa fel a Mikrotik Router",
"description": "Állítsd be a Mikrotik Router integrációt.",
"data": {
"name": "Integráció neve",
@ -11,7 +10,8 @@
"port": "Port",
"username": "Felhasználónév",
"password": "Jelszó",
"ssl": "SSL használata"
"ssl": "SSL használata",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Ügyfél MAC- és IP-címének mutatása az interfészeken",
"unit_of_measurement": "Mértékegység",
"track_network_hosts_timeout": "Hálózati eszközök követésének időtúllépése (másodperc)",
"zone": "Zone for device tracker"
"zone": "Zóna az eszközkövető számára"
},
"title": "Alapbeállítások"
"title": "Mikrotik Router opciók (1\/2)",
"description": "Az integráció konfigurálása"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "NAT kapcsolók",
"sensor_scripts": "Szkript kapcsolók",
"sensor_environment": "Környezeti változó érzékelők",
"sensor_pppusers": "PPP felhasználók",
"sensor_kidcontrol": "Szülői felügyelet",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_mangle": "Mangle kapcsolók",
"sensor_ppp": "PPP felhasználók",
"sensor_filter": "Szűrő kapcsolók",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Válassz érzékelőket"
"title": "Mikrotik Router opciók (2\/2)",
"description": "Érzékelők és kapcsolók engedélyezése"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Setja upp Mikrotik Router",
"description": "Setja upp Mikrotik Router samþættingu.",
"data": {
"name": "Heiti samþættingar",
@ -11,7 +10,8 @@
"port": "Port",
"username": "Notandanafn",
"password": "Lykilorð",
"ssl": "Nota SSL"
"ssl": "Nota SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Sýna MAC og IP vistfang fyrir biðlara á netkortum",
"unit_of_measurement": "Mælieining",
"track_network_hosts_timeout": "Tímamörk við rakningu á netbúnaði ( sekúndur )",
"zone": "Zone for device tracker"
"zone": "Svæði fyrir tækjarakningu"
},
"title": "Grunnvalkostir"
"title": "Mikrotik Router valkostir (1\/2)",
"description": "Stilla samþættingu"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "NAT rofar",
"sensor_scripts": "Skrifturofar",
"sensor_environment": "Umhverfisbreytu skynjarar",
"sensor_pppusers": "PPP notendur",
"sensor_kidcontrol": "Krakkastjórnun",
"sensor_mangle": "Mangle switches",
"sensor_mangle": "Bjögunarrofi",
"sensor_ppp": "PPP notendur",
"sensor_filter": "Filter switches"
"sensor_filter": "Síurofar",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Veldu skynjara"
"title": "Mikrotik Router valkostir (2\/2)",
"description": "Virkja skynjara og rofa"
}
}
}

View file

@ -1,17 +1,17 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"description": "Imposta integrazione Router Mikrotik",
"title": "Configurare il Mikrotik Router",
"description": "Imposta integrazione Mikrotik Router",
"data": {
"name": "Nome integrazione",
"host": "Host",
"port": "Porta",
"username": "Nome utente",
"password": "Password",
"ssl": "Usa SSL"
"ssl": "Usa SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Mostra client MAC e IP sulle interfacce",
"unit_of_measurement": "Unità di misura",
"track_network_hosts_timeout": "Traccia timeout dei dispositivi di rete (secondi)",
"zone": "Zone for device tracker"
"zone": "Zona per localizzatore dispositivo"
},
"title": "Opzioni di base"
"title": "Opzioni Mikrotik Router (1\/2)",
"description": "Configura integrazione"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "Commutatori NAT",
"sensor_scripts": "Commutatori di script",
"sensor_environment": "Sensori di variabili ambientali",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Controllo bambini",
"sensor_mangle": "Interruttori a manganello",
"sensor_ppp": "Utenti PPP",
"sensor_filter": "Interruttori del filtro",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Seleziona i sensori"
"title": "Opzioni Mikrotik Router (2\/2)",
"description": "Abilita sensori e interruttori"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Mikrotik Routerのセットアップ",
"description": "Mikrotikルータ統合をセットアップします。",
"data": {
"name": "統合名",
@ -11,7 +10,8 @@
"port": "ポート",
"username": "ユーザ名",
"password": "パスワード",
"ssl": "SSLを使用します"
"ssl": "SSLを使用します",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,7 +20,8 @@
"cannot_connect": "Mikrotikに接続できません。",
"ssl_handshake_failure": "SSLハンドシェイクに失敗しました",
"connection_timeout": "Mikrotik接続がタイムアウトしました。",
"wrong_login": "ユーザ名またはパスワードが無効です。"
"wrong_login": "ユーザ名またはパスワードが無効です。",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -31,9 +32,10 @@
"track_iface_clients": "クライアントのMACとインターフェイスのIPを表示します",
"unit_of_measurement": "測定単位",
"track_network_hosts_timeout": "ネットワーク デバイスのタイムアウト(秒)をトラッキングします",
"zone": "Zone for device tracker"
"zone": "デバイス追跡装置用ゾーン"
},
"title": "基本オプション"
"title": "Mikrotik Router オプション (1\/2)",
"description": "インテグレーションを設定する"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "NAT スイッチ",
"sensor_scripts": "スクリプトスイッチ",
"sensor_environment": "環境変数センサー",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "キッドコントロール",
"sensor_mangle": "マングルスイッチ",
"sensor_ppp": "PPPユーザー",
"sensor_filter": "フィルタースイッチ",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "センサーを選択"
"title": "Mikrotik Router オプション (2\/2)",
"description": "センサーとスイッチを有効にする"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Mikrotik Router 설정",
"description": "Mikrotik 라우터 통합을 설정합니다.",
"data": {
"name": "통합 이름",
@ -11,7 +10,8 @@
"port": "포트",
"username": "사용자 이름",
"password": "비밀번호",
"ssl": "SSL 사용"
"ssl": "SSL 사용",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,7 +20,8 @@
"cannot_connect": "Mikrotik에 연결할 수 없습니다.",
"ssl_handshake_failure": "SSL 핸드셰이크 실패",
"connection_timeout": "Mikrotik 연결 시간이 초과되었습니다.",
"wrong_login": "사용자 이름 또는 비밀번호가 유효하지 않습니다."
"wrong_login": "사용자 이름 또는 비밀번호가 유효하지 않습니다.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -31,9 +32,10 @@
"track_iface_clients": "인터페이스에 클라이언트 MAC 및 IP 표시",
"unit_of_measurement": "측정 단위",
"track_network_hosts_timeout": "네트워크 기기 시간 초과 추적(초)",
"zone": "Zone for device tracker"
"zone": "기기 추적기용 지역"
},
"title": "기본 설정"
"title": "Mikrotik Router 옵션(1\/2)",
"description": "통합 환경설정"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "내트 스위치",
"sensor_scripts": "스크립트 스위치",
"sensor_environment": "환경 변수 센서",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "어린이 이용 제어",
"sensor_mangle": "맹글 스위치",
"sensor_ppp": "PPP 사용자",
"sensor_filter": "필터 스위치",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "센서 선택"
"title": "Mikrotik Router 옵션(2\/2)",
"description": "센서 및 스위치 활성화"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Lestatiet Mikrotik Router",
"description": "Mikrotik maršrutētāja integrācijas konfigurēšana.",
"data": {
"name": "Integrācijas nosaukums",
@ -11,7 +10,8 @@
"port": "Ports",
"username": "Lietotājvārds",
"password": "Parole",
"ssl": "Izmantot SSL"
"ssl": "Izmantot SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Rādīt klienta MAC un IP interfeisā",
"unit_of_measurement": "Mērvienības",
"track_network_hosts_timeout": "Tīkla ierīces uzraudzības noilgums (sekundēs)",
"zone": "Zone for device tracker"
"zone": "Lerīces izsekotāja zona"
},
"title": "Pamata parametri"
"title": "Mikrotik Router opcijas (1\/2)",
"description": "Konfigurēt integrāciju"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "NAT kārtulu slēdži",
"sensor_scripts": "Skriptu slēdži",
"sensor_environment": "Mainīgie vides sensori",
"sensor_pppusers": "PPP lietotāji",
"sensor_kidcontrol": "Bērnu kontrole",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_mangle": "Manglu slēdži",
"sensor_ppp": "PPP lietotāji",
"sensor_filter": "Filtra slēdži",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Sensora izvēle"
"title": "Mikrotik Router opcijas (2\/2)",
"description": "Sensoru un slēdžu aktivizēšana"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Mikrotik Router instellen",
"description": "Stel Mikrotik Router-integratie in.",
"data": {
"name": "Naam van de integratie",
@ -11,7 +10,8 @@
"port": "Poort",
"username": "Gebruikersnaam",
"password": "Wachtwoord",
"ssl": "Gebruik SSL"
"ssl": "Gebruik SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Mac en IP van client weergeven op interfaces",
"unit_of_measurement": "Meet eenheid",
"track_network_hosts_timeout": "Volg de time-out van netwerkapparaten (seconden)",
"zone": "Zone for device tracker"
"zone": "Zone voor apparaattracker"
},
"title": "Basisopties"
"title": "Opties Mikrotik Router (1\/2)",
"description": "Integratie configureren"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "NAT-switches",
"sensor_scripts": "Script-switches",
"sensor_environment": "Omgevingsvariabelesensors",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Kindercontrole",
"sensor_mangle": "Mangelschakelaars",
"sensor_ppp": "PPP-gebruikers",
"sensor_filter": "Filterschakelaars",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Selecteer 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

@ -1,17 +1,17 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"description": "Skonfiguruj integrację z routerem Mikrotik.",
"title": "Skonfiguruj Mikrotik Router",
"description": "Skonfiguruj integrację z Mikrotik Router.",
"data": {
"name": "Nazwa integracji",
"host": "Host",
"port": "Port",
"username": "Nazwa użytkownika",
"password": "Hasło",
"ssl": "Użyj SSL"
"ssl": "Użyj SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Pokaż w interfejsach adres MAC i IP klienta",
"unit_of_measurement": "Jednostka miary",
"track_network_hosts_timeout": "Śledź limity czasu dla urządzeń sieciowych (sekundy)",
"zone": "Zone for device tracker"
"zone": "Strefa programu do śledzenia urządzeń"
},
"title": "Podstawowe opcje"
"title": "Opcje Mikrotik Router (1\/2)",
"description": "Skonfiguruj integrację"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "Przełączniki NAT",
"sensor_scripts": "Przełączniki skryptów",
"sensor_environment": "Czujniki zmiennych środowiskowych",
"sensor_pppusers": "Użytkownicy PPP",
"sensor_kidcontrol": "Kontrola rodzicielska",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_mangle": "Przełączniki mangle",
"sensor_ppp": "Użytkownicy PPP",
"sensor_filter": "Przełączniki filtrów",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Wybierz czujniki"
"title": "Opcje Mikrotik Router (2\/2)",
"description": "Włącz czujniki i przełączniki"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Configurar o Mikrotik Router",
"description": "Configurar a integração do Mikrotik Router.",
"data": {
"name": "Nome da integração",
@ -11,7 +10,8 @@
"port": "Porta",
"username": "Nome de utilizador",
"password": "Palavra-passe",
"ssl": "Usar SSL"
"ssl": "Usar SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Mostrar MAC e IP do cliente nas interfaces",
"unit_of_measurement": "Unidade de medida",
"track_network_hosts_timeout": "Rastrear o tempo limite dos dispositivos de rede (segundos)",
"zone": "Zone for device tracker"
"zone": "Zona para o rastreador do dispositivo"
},
"title": "Opções básicas"
"title": "Opções do Mikrotik Router (1\/2)",
"description": "Configurar integração"
},
"sensor_select": {
"data": {
@ -43,15 +45,17 @@
"sensor_client_traffic": "Sensores de tráfego do cliente",
"sensor_simple_queues": "Comutadores simples de filas de espera",
"sensor_nat": "Comutadores NAT",
"sensor_scripts": "Comutadores de scripts",
"sensor_scripts": "Comutadores de script",
"sensor_environment": "Sensores de variáveis de ambiente",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Controlo de crianças",
"sensor_mangle": "Interruptores mangle",
"sensor_ppp": "Utilizadores PPP",
"sensor_filter": "Interruptores de filtro",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Selecionar os sensores"
"title": "Opções do Mikrotik Router (2\/2)",
"description": "Ativar sensores e interruptores"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Configuração do Mikrotik Router",
"description": "Configurando uma integração de roteador (Mikrotik).",
"data": {
"name": "Nome da mesclagem",
@ -11,7 +10,8 @@
"port": "Porta",
"username": "Nome de usuário",
"password": "Senha",
"ssl": "Usar SSL"
"ssl": "Usar SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -33,7 +34,8 @@
"track_network_hosts_timeout": "Rastrear o tempo limite dos dispositivos de rede (segundos)",
"zone": "Zona para rastreador de dispositivos"
},
"title": "Opções básicas"
"title": "Opções de roteador Mikrotik (1\/2)",
"description": "Inicialize a integração"
},
"sensor_select": {
"data": {
@ -43,15 +45,17 @@
"sensor_client_traffic": "Sensores de tráfego do cliente",
"sensor_simple_queues": "Interruptores de filas simples",
"sensor_nat": "Interruptores NAT",
"sensor_scripts": "Interruptores de scripts",
"sensor_scripts": "Interruptores de script",
"sensor_environment": "Sensores de variáveis de ambiente",
"sensor_pppusers": "Usuários de PPP",
"sensor_kidcontrol": "Controle infantil",
"sensor_mangle": "Interruptores Mangle",
"sensor_ppp": "Usuários de PPP",
"sensor_filter": "Interruptores de filtro"
"sensor_filter": "Interruptores de filtro",
"sensor_client_captive": "sensores de cliente cativo",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Selecione os sensores"
"title": "Opções de roteador Mikrotik (2\/2)",
"description": "Ativar sensores e interruptores"
}
}
}

View file

@ -1,17 +1,17 @@
{
"config": {
"title": "Роутер Mikrotik",
"step": {
"user": {
"title": "Роутер Mikrotik",
"title": "Настроить Mikrotik Router",
"description": "Настройка Home Assistant для интеграции с Mikrotik.",
"data": {
"name": "Название интеграции",
"host": "Адрес маршрутизатора",
"host": "Адрес хоста",
"port": "Порт",
"username": "Имя пользователя",
"password": "Пароль",
"ssl": "Использовать SSL"
"ssl": "Использовать SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,7 +20,8 @@
"cannot_connect": "Не удалось подключиться.",
"ssl_handshake_failure": "Ошибка SSL-соединения.",
"connection_timeout": "Истекло время подключения к Mikrotik.",
"wrong_login": "Неверное имя пользователя или пароль."
"wrong_login": "Неверное имя пользователя или пароль.",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -33,7 +34,8 @@
"track_network_hosts_timeout": "Таймаут отслеживания сетевых устройств (в секундах)",
"zone": "Зона для отслеживания устройств"
},
"title": "Базовые параметры"
"title": "Параметры Mikrotik Router (1\/2)",
"description": "Настроить интеграцию"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "Переключатели NAT правил",
"sensor_scripts": "Переключатели скриптов",
"sensor_environment": "Сенсоры переменных окружения",
"sensor_pppusers": "PPP пользователи",
"sensor_kidcontrol": "Родительский контроль",
"sensor_mangle": "Переключатели правил Mangle",
"sensor_mangle": "Переключатели Mangle",
"sensor_ppp": "PPP-пользователи",
"sensor_filter": "Переключатели фильтров"
"sensor_filter": "Переключатели фильтров",
"sensor_client_captive": "Данные портала авторизации",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Выберите сенсоры"
"title": "Параметры Mikrotik Router (2\/2)",
"description": "Включить датчики и переключатели"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Nastaviť Mikrotik Router",
"description": "Nastaviť integráciu Mikrotik Router.",
"data": {
"name": "Názov integrácie",
@ -11,7 +10,8 @@
"port": "Port",
"username": "Užívateľ",
"password": "Heslo",
"ssl": "Použiť SSL"
"ssl": "Použiť SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Zobraziť klientske MAC a IP na rozhraniach",
"unit_of_measurement": "Merná jednotka",
"track_network_hosts_timeout": "Časový limit sledovania sieťových zariadení (sekundy)",
"zone": "Zone for device tracker"
"zone": "Zóna pre sledované zariadenia"
},
"title": "Základné možnosti"
"title": "Možnosti Mikrotik Router (1\/2)",
"description": "Nakonfigurovať integráciu"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "Prepínače NAT",
"sensor_scripts": "Prepínače skriptov",
"sensor_environment": "Snímače prostredia",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Detská kontrola",
"sensor_mangle": "Mangle prepínače",
"sensor_ppp": "PPP používatelia",
"sensor_filter": "Prepínače filtrov",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Vybrať senzory"
"title": "Možnosti Mikrotik Router (2\/2)",
"description": "Povoliť senzory a spínače"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Mikrotik Router'ı Kur",
"description": "Mikrotik yönlendirici entegrasyonunu kur",
"data": {
"name": "Entegrasyon adı",
@ -11,7 +10,8 @@
"port": "Port",
"username": "Kullanıcı adı",
"password": "Şifre",
"ssl": "SSL kullan"
"ssl": "SSL kullan",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Müşteri MAC'ini ve IP'sini arayüzlerde göster",
"unit_of_measurement": "Ölçü birimi",
"track_network_hosts_timeout": "Ağ cihazı izleme zaman aşımı (saniyeler)",
"zone": "Zone for device tracker"
"zone": "Cihaz izleyici için alan"
},
"title": "Temel seçenekler"
"title": "Mikrotik Router seçenekleri (1\/2)",
"description": "Entegrasyonu yapılandır"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "NAT switchler",
"sensor_scripts": "Betik switchleri",
"sensor_environment": "Çevre değişkeni sensörleri",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Çocuk denetimi",
"sensor_mangle": "Mangle anahtarları",
"sensor_ppp": "PPP kullanıcıları",
"sensor_filter": "Filtre anahtarları",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Sensörleri seçin"
"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

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "Thiết lập Mikrotik Router",
"description": "Thiết lập tích hợp Bộ định tuyến Mikrotik.",
"data": {
"name": "Tên của tích hợp",
@ -11,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"
}
}
},
@ -20,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": {
@ -31,9 +32,10 @@
"track_iface_clients": "Hiển thị MAC và IP của máy khách trên giao diện",
"unit_of_measurement": "Đơn vị đo",
"track_network_hosts_timeout": "Theo dõi thời gian chờ thiết bị mạng (giây)",
"zone": "Zone for device tracker"
"zone": "Khu vực cho trình theo dõi thiết bị"
},
"title": "Tùy chọn cơ bản"
"title": "Cài đặt Mikrotik Router (1\/2)",
"description": "Đặt cấu hình tích hợp"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "Bộ chuyển đổi NAT",
"sensor_scripts": "Bộ chuyển đổi mã lệnh",
"sensor_environment": "Cảm biến biến số môi trường",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "Kiểm soát trẻ em",
"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_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "Chọn cảm biến"
"title": "Cài đặt Mikrotik Router (2\/2)",
"description": "Bật cảm biến và công tắc"
}
}
}

View file

@ -1,9 +1,8 @@
{
"config": {
"title": "Mikrotik Router",
"step": {
"user": {
"title": "Mikrotik Router",
"title": "设置Mikrotik Router",
"description": "设置 Mikrotik 路由器集成。",
"data": {
"name": "集成名称",
@ -11,7 +10,8 @@
"port": "端口",
"username": "用户名",
"password": "密码",
"ssl": "使用 SSL"
"ssl": "使用 SSL",
"verify_ssl": "Verify SSL"
}
}
},
@ -20,7 +20,8 @@
"cannot_connect": "无法连接到 Mikrotik。",
"ssl_handshake_failure": "SSL 交握失败",
"connection_timeout": "Mikrotik 连接超时。",
"wrong_login": "无效的用户名或密码。"
"wrong_login": "无效的用户名或密码。",
"ssl_verify_failure": "Certificate verify failed"
}
},
"options": {
@ -31,9 +32,10 @@
"track_iface_clients": "显示接口上的客户端 MAC 和 IP",
"unit_of_measurement": "测量单位",
"track_network_hosts_timeout": "跟踪网络设备超时(秒)",
"zone": "Zone for device tracker"
"zone": "设备跟踪器的区域"
},
"title": "基本选项"
"title": "Mikrotik Router 选项 (1\/2)",
"description": "配置集成"
},
"sensor_select": {
"data": {
@ -45,13 +47,15 @@
"sensor_nat": "NAT 交换机",
"sensor_scripts": "脚本交换机",
"sensor_environment": "环境变量传感器",
"sensor_pppusers": "PPP users",
"sensor_kidcontrol": "Kid control",
"sensor_mangle": "Mangle switches",
"sensor_ppp": "PPP users",
"sensor_filter": "Filter switches"
"sensor_kidcontrol": "儿童控制",
"sensor_mangle": "标记开关",
"sensor_ppp": "PPP 用户",
"sensor_filter": "过滤器开关",
"sensor_client_captive": "Captive portal data",
"sensor_netwatch_tracker": "Netwatch tracker sensors"
},
"title": "选择传感器"
"title": "Mikrotik Router 选项 (2\/2)",
"description": "启动传感器和开关"
}
}
}

View file

@ -0,0 +1,209 @@
"""Support for the Mikrotik Router update service."""
from __future__ import annotations
import asyncio
from logging import getLogger
from typing import Any
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 .coordinator import MikrotikCoordinator
from .entity import MikrotikEntity, async_add_entities
from .update_types import (
SENSOR_TYPES,
SENSOR_SERVICES,
)
from packaging.version import Version
_LOGGER = getLogger(__name__)
DEVICE_UPDATE = "device_update"
# ---------------------------
# async_setup_entry
# ---------------------------
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 async_add_entities(hass, config_entry, dispatcher)
# ---------------------------
# MikrotikRouterOSUpdate
# ---------------------------
class MikrotikRouterOSUpdate(MikrotikEntity, UpdateEntity):
"""Define an Mikrotik Controller Update entity."""
def __init__(
self,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
"""Set up device update entity."""
super().__init__(coordinator, entity_description, uid)
self._attr_supported_features = UpdateEntityFeature.INSTALL
self._attr_supported_features |= UpdateEntityFeature.BACKUP
self._attr_supported_features |= UpdateEntityFeature.RELEASE_NOTES
self._attr_title = self.entity_description.title
@property
def is_on(self) -> bool:
"""Return true if device is on."""
return self._data[self.entity_description.data_attribute]
@property
def installed_version(self) -> str:
"""Version installed and in use."""
return self._data["installed-version"]
@property
def latest_version(self) -> str:
"""Latest version available for install."""
return self._data["latest-version"]
async def options_updated(self) -> None:
"""No action needed."""
async def async_install(self, version: str, backup: bool, **kwargs: Any) -> None:
"""Install an update."""
if backup:
self.coordinator.execute("/system/backup", "save", None, None)
self.coordinator.execute("/system/package/update", "install", None, None)
async def async_release_notes(self) -> str:
"""Return the release notes."""
try:
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"]
)
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 "Error fetching release notes."
@property
def release_url(self) -> str:
"""URL to the full release notes of the latest version available."""
return "https://mikrotik.com/download/changelogs"
# ---------------------------
# MikrotikRouterBoardFWUpdate
# ---------------------------
class MikrotikRouterBoardFWUpdate(MikrotikEntity, UpdateEntity):
"""Define an Mikrotik Controller Update entity."""
TYPE = DEVICE_UPDATE
_attr_device_class = UpdateDeviceClass.FIRMWARE
def __init__(
self,
coordinator: MikrotikCoordinator,
entity_description,
uid: str | None = None,
):
"""Set up device update entity."""
super().__init__(coordinator, entity_description, uid)
self._attr_supported_features = UpdateEntityFeature.INSTALL
self._attr_title = self.entity_description.title
@property
def is_on(self) -> bool:
"""Return true if device is on."""
return (
self.data["routerboard"]["current-firmware"]
!= self.data["routerboard"]["upgrade-firmware"]
)
@property
def installed_version(self) -> str:
"""Version installed and in use."""
return self._data["current-firmware"]
@property
def latest_version(self) -> str:
"""Latest version available for install."""
return self._data["upgrade-firmware"]
async def options_updated(self) -> None:
"""No action needed."""
async def async_install(self, version: str, backup: bool, **kwargs: Any) -> None:
"""Install an update."""
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

@ -0,0 +1,56 @@
"""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
@dataclass
class MikrotikUpdateEntityDescription(UpdateEntityDescription):
"""Class describing mikrotik entities."""
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 | 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 = "MikrotikRouterOSUpdate"
SENSOR_TYPES: tuple[MikrotikUpdateEntityDescription, ...] = (
MikrotikUpdateEntityDescription(
key="system_rosupdate",
name="RouterOS update",
ha_group="System",
title="Mikrotik RouterOS",
data_path="fw-update",
data_name="",
data_uid="",
data_reference="",
func="MikrotikRouterOSUpdate",
),
MikrotikUpdateEntityDescription(
key="system_rbfwupdate",
name="RouterBOARD firmware update",
ha_group="System",
title="Mikrotik RouterBOARD",
data_path="routerboard",
data_attribute="current-firmware",
data_name="",
data_uid="",
data_reference="",
func="MikrotikRouterBoardFWUpdate",
),
)
SENSOR_SERVICES = {}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View file

@ -1,8 +1,6 @@
{
"name": "Mikrotik Router",
"homeassistant": "2022.2.0",
"iot_class": "local_poll",
"domains": ["device_tracker", "switch", "button", "sensor", "binary_sensor"],
"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

View file

@ -1,20 +0,0 @@
"""Tests for the Mikrotik Router component."""
from custom_components.mikrotik_router import config_flow
MOCK_DATA = {
config_flow.CONF_NAME: config_flow.DEFAULT_DEVICE_NAME,
config_flow.CONF_HOST: config_flow.DEFAULT_HOST,
config_flow.CONF_USERNAME: config_flow.DEFAULT_USERNAME,
config_flow.CONF_PASSWORD: config_flow.DEFAULT_PASSWORD,
config_flow.CONF_PORT: config_flow.DEFAULT_PORT,
config_flow.CONF_SSL: config_flow.DEFAULT_SSL,
}
MOCK_OPTIONS = {
config_flow.CONF_SCAN_INTERVAL: config_flow.DEFAULT_SCAN_INTERVAL,
config_flow.CONF_UNIT_OF_MEASUREMENT: config_flow.DEFAULT_UNIT_OF_MEASUREMENT,
config_flow.CONF_TRACK_IFACE_CLIENTS: config_flow.DEFAULT_TRACK_IFACE_CLIENTS,
config_flow.CONF_TRACK_HOSTS: config_flow.DEFAULT_TRACK_HOSTS,
config_flow.CONF_TRACK_HOSTS_TIMEOUT: config_flow.DEFAULT_TRACK_HOST_TIMEOUT,
}

File diff suppressed because it is too large Load diff

View file

@ -1,253 +0,0 @@
"""Set up some common test helper things."""
import functools
import logging
from unittest.mock import patch
import pytest
import requests_mock as _requests_mock
from homeassistant import util
from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY
from homeassistant.auth.providers import homeassistant, legacy_api_password
from homeassistant.components.websocket_api.auth import (
TYPE_AUTH,
TYPE_AUTH_OK,
TYPE_AUTH_REQUIRED,
)
from homeassistant.components.websocket_api.http import URL
from homeassistant.exceptions import ServiceNotFound
from homeassistant.setup import async_setup_component
from homeassistant.util import location
from tests.ignore_uncaught_exceptions import (
IGNORE_UNCAUGHT_EXCEPTIONS,
IGNORE_UNCAUGHT_JSON_EXCEPTIONS,
)
pytest.register_assert_rewrite("tests.common")
from tests.common import ( # noqa: E402, isort:skip
CLIENT_ID,
INSTANCES,
MockUser,
async_test_home_assistant,
mock_coro,
mock_storage as mock_storage,
)
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
def check_real(func):
"""Force a function to require a keyword _test_real to be passed in."""
@functools.wraps(func)
async def guard_func(*args, **kwargs):
real = kwargs.pop("_test_real", None)
if not real:
raise Exception(
'Forgot to mock or pass "_test_real=True" to %s', func.__name__
)
return await func(*args, **kwargs)
return guard_func
# Guard a few functions that would make network connections
location.async_detect_location_info = check_real(location.async_detect_location_info)
util.get_local_ip = lambda: "127.0.0.1"
@pytest.fixture(autouse=True)
def verify_cleanup():
"""Verify that the test has cleaned up resources correctly."""
yield
if len(INSTANCES) >= 2:
count = len(INSTANCES)
for inst in INSTANCES:
inst.stop()
pytest.exit(f"Detected non stopped instances ({count}), aborting test run")
@pytest.fixture
def hass_storage():
"""Fixture to mock storage."""
with mock_storage() as stored_data:
yield stored_data
@pytest.fixture
def hass(loop, hass_storage, request):
"""Fixture to provide a test instance of Home Assistant."""
def exc_handle(loop, context):
"""Handle exceptions by rethrowing them, which will fail the test."""
exceptions.append(context["exception"])
orig_exception_handler(loop, context)
exceptions = []
hass = loop.run_until_complete(async_test_home_assistant(loop))
orig_exception_handler = loop.get_exception_handler()
loop.set_exception_handler(exc_handle)
yield hass
loop.run_until_complete(hass.async_stop(force=True))
for ex in exceptions:
if (
request.module.__name__,
request.function.__name__,
) in IGNORE_UNCAUGHT_EXCEPTIONS:
continue
if isinstance(ex, ServiceNotFound):
continue
if (
isinstance(ex, TypeError)
and "is not JSON serializable" in str(ex)
and (request.module.__name__, request.function.__name__)
in IGNORE_UNCAUGHT_JSON_EXCEPTIONS
):
continue
raise ex
@pytest.fixture
def requests_mock():
"""Fixture to provide a requests mocker."""
with _requests_mock.mock() as m:
yield m
@pytest.fixture
def mock_device_tracker_conf():
"""Prevent device tracker from reading/writing data."""
devices = []
async def mock_update_config(path, id, entity):
devices.append(entity)
with patch(
"homeassistant.components.device_tracker.legacy"
".DeviceTracker.async_update_config",
side_effect=mock_update_config,
), patch(
"homeassistant.components.device_tracker.legacy.async_load_config",
side_effect=lambda *args: mock_coro(devices),
):
yield devices
@pytest.fixture
def hass_access_token(hass, hass_admin_user):
"""Return an access token to access Home Assistant."""
refresh_token = hass.loop.run_until_complete(
hass.auth.async_create_refresh_token(hass_admin_user, CLIENT_ID)
)
return hass.auth.async_create_access_token(refresh_token)
@pytest.fixture
def hass_owner_user(hass, local_auth):
"""Return a Home Assistant admin user."""
return MockUser(is_owner=True).add_to_hass(hass)
@pytest.fixture
def hass_admin_user(hass, local_auth):
"""Return a Home Assistant admin user."""
admin_group = hass.loop.run_until_complete(
hass.auth.async_get_group(GROUP_ID_ADMIN)
)
return MockUser(groups=[admin_group]).add_to_hass(hass)
@pytest.fixture
def hass_read_only_user(hass, local_auth):
"""Return a Home Assistant read only user."""
read_only_group = hass.loop.run_until_complete(
hass.auth.async_get_group(GROUP_ID_READ_ONLY)
)
return MockUser(groups=[read_only_group]).add_to_hass(hass)
@pytest.fixture
def hass_read_only_access_token(hass, hass_read_only_user):
"""Return a Home Assistant read only user."""
refresh_token = hass.loop.run_until_complete(
hass.auth.async_create_refresh_token(hass_read_only_user, CLIENT_ID)
)
return hass.auth.async_create_access_token(refresh_token)
@pytest.fixture
def legacy_auth(hass):
"""Load legacy API password provider."""
prv = legacy_api_password.LegacyApiPasswordAuthProvider(
hass,
hass.auth._store,
{"type": "legacy_api_password", "api_password": "test-password"},
)
hass.auth._providers[(prv.type, prv.id)] = prv
return prv
@pytest.fixture
def local_auth(hass):
"""Load local auth provider."""
prv = homeassistant.HassAuthProvider(
hass, hass.auth._store, {"type": "homeassistant"}
)
hass.auth._providers[(prv.type, prv.id)] = prv
return prv
@pytest.fixture
def hass_client(hass, aiohttp_client, hass_access_token):
"""Return an authenticated HTTP client."""
async def auth_client():
"""Return an authenticated client."""
return await aiohttp_client(
hass.http.app, headers={"Authorization": f"Bearer {hass_access_token}"}
)
return auth_client
@pytest.fixture
def hass_ws_client(aiohttp_client, hass_access_token, hass):
"""Websocket client fixture connected to websocket server."""
async def create_client(hass=hass, access_token=hass_access_token):
"""Create a websocket client."""
assert await async_setup_component(hass, "websocket_api", {})
client = await aiohttp_client(hass.http.app)
with patch("homeassistant.components.http.auth.setup_auth"):
websocket = await client.ws_connect(URL)
auth_resp = await websocket.receive_json()
assert auth_resp["type"] == TYPE_AUTH_REQUIRED
if access_token is None:
await websocket.send_json(
{"type": TYPE_AUTH, "access_token": "incorrect"}
)
else:
await websocket.send_json(
{"type": TYPE_AUTH, "access_token": access_token}
)
auth_ok = await websocket.receive_json()
assert auth_ok["type"] == TYPE_AUTH_OK
# wrap in client
websocket.client = client
return websocket
return create_client

View file

@ -1,38 +0,0 @@
"""List of modules that have uncaught exceptions today. Will be shrunk over time."""
IGNORE_UNCAUGHT_EXCEPTIONS = [
("tests.components.dyson.test_air_quality", "test_purecool_aiq_attributes"),
("tests.components.dyson.test_air_quality", "test_purecool_aiq_update_state"),
(
"tests.components.dyson.test_air_quality",
"test_purecool_component_setup_only_once",
),
("tests.components.dyson.test_air_quality", "test_purecool_aiq_without_discovery"),
(
"tests.components.dyson.test_air_quality",
"test_purecool_aiq_empty_environment_state",
),
(
"tests.components.dyson.test_climate",
"test_setup_component_with_parent_discovery",
),
("tests.components.dyson.test_fan", "test_purecoollink_attributes"),
("tests.components.dyson.test_fan", "test_purecool_turn_on"),
("tests.components.dyson.test_fan", "test_purecool_set_speed"),
("tests.components.dyson.test_fan", "test_purecool_turn_off"),
("tests.components.dyson.test_fan", "test_purecool_set_dyson_speed"),
("tests.components.dyson.test_fan", "test_purecool_oscillate"),
("tests.components.dyson.test_fan", "test_purecool_set_night_mode"),
("tests.components.dyson.test_fan", "test_purecool_set_auto_mode"),
("tests.components.dyson.test_fan", "test_purecool_set_angle"),
("tests.components.dyson.test_fan", "test_purecool_set_flow_direction_front"),
("tests.components.dyson.test_fan", "test_purecool_set_timer"),
("tests.components.dyson.test_fan", "test_purecool_update_state"),
("tests.components.dyson.test_fan", "test_purecool_update_state_filter_inv"),
("tests.components.dyson.test_fan", "test_purecool_component_setup_only_once"),
("tests.components.dyson.test_sensor", "test_purecool_component_setup_only_once"),
("tests.components.ios.test_init", "test_creating_entry_sets_up_sensor"),
("tests.components.ios.test_init", "test_not_configuring_ios_not_creates_entry"),
("tests.components.local_file.test_camera", "test_file_not_readable"),
]
IGNORE_UNCAUGHT_JSON_EXCEPTIONS = []

View file

@ -1,186 +0,0 @@
from datetime import timedelta
from unittest.mock import patch
import librouteros
import pytest
from homeassistant import data_entry_flow
from custom_components import mikrotik_router
from homeassistant.const import (
CONF_NAME,
CONF_HOST,
CONF_PORT,
CONF_USERNAME,
CONF_PASSWORD,
CONF_SSL,
)
from . import MOCK_DATA
from tests.common import MockConfigEntry
DEMO_USER_INPUT = {
CONF_NAME: "Home router",
CONF_HOST: "0.0.0.0",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
CONF_PORT: 8278,
CONF_SSL: True,
}
DEMO_CONFIG_ENTRY = {
CONF_NAME: "Home router",
CONF_HOST: "0.0.0.0",
CONF_USERNAME: "username",
CONF_PASSWORD: "password",
CONF_PORT: 8278,
CONF_SSL: True,
mikrotik_router.mikrotik_controller.CONF_SCAN_INTERVAL: 60,
mikrotik_router.mikrotik_controller.CONF_UNIT_OF_MEASUREMENT: "Mbps",
mikrotik_router.mikrotik_controller.CONF_TRACK_IFACE_CLIENTS: True,
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS: True,
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS_TIMEOUT: 180,
}
@pytest.fixture(name="api")
def mock_mikrotik_api():
"""Mock an api."""
with patch("librouteros.connect"):
yield
@pytest.fixture(name="auth_error")
def mock_api_authentication_error():
"""Mock an api."""
with patch(
"librouteros.connect",
side_effect=librouteros.exceptions.TrapError("invalid user name or password"),
):
yield
@pytest.fixture(name="conn_error")
def mock_api_connection_error():
"""Mock an api."""
with patch(
"librouteros.connect", side_effect=librouteros.exceptions.ConnectionClosed
):
yield
async def test_import(hass, api):
"""Test import step."""
result = await hass.config_entries.flow.async_init(
mikrotik_router.DOMAIN, context={"source": "import"}, data=MOCK_DATA
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Mikrotik"
assert result["data"][CONF_NAME] == "Mikrotik"
assert result["data"][CONF_HOST] == "10.0.0.1"
assert result["data"][CONF_USERNAME] == "admin"
assert result["data"][CONF_PASSWORD] == "admin"
assert result["data"][CONF_PORT] == 0
assert result["data"][CONF_SSL] is False
async def test_flow_works(hass, api):
"""Test config flow."""
result = await hass.config_entries.flow.async_init(
mikrotik_router.DOMAIN, context={"source": "user"}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=DEMO_USER_INPUT
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "Home router"
assert result["data"][CONF_NAME] == "Home router"
assert result["data"][CONF_HOST] == "0.0.0.0"
assert result["data"][CONF_USERNAME] == "username"
assert result["data"][CONF_PASSWORD] == "password"
assert result["data"][CONF_PORT] == 8278
assert result["data"][CONF_SSL] is True
async def test_options(hass):
"""Test updating options."""
entry = MockConfigEntry(domain=mikrotik_router.DOMAIN, data=DEMO_CONFIG_ENTRY)
entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "device_tracker"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
mikrotik_router.mikrotik_controller.CONF_SCAN_INTERVAL: 30,
mikrotik_router.mikrotik_controller.CONF_UNIT_OF_MEASUREMENT: "Kbps",
mikrotik_router.mikrotik_controller.CONF_TRACK_IFACE_CLIENTS: True,
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS: False,
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS_TIMEOUT: 180,
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["data"] == {
mikrotik_router.mikrotik_controller.CONF_SCAN_INTERVAL: 30,
mikrotik_router.mikrotik_controller.CONF_UNIT_OF_MEASUREMENT: "Kbps",
mikrotik_router.mikrotik_controller.CONF_TRACK_IFACE_CLIENTS: True,
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS: False,
mikrotik_router.mikrotik_controller.CONF_TRACK_HOSTS_TIMEOUT: 180,
}
async def test_name_exists(hass, api):
"""Test name already configured."""
entry = MockConfigEntry(domain=mikrotik_router.DOMAIN, data=DEMO_CONFIG_ENTRY)
entry.add_to_hass(hass)
user_input = DEMO_USER_INPUT.copy()
user_input[CONF_HOST] = "0.0.0.1"
result = await hass.config_entries.flow.async_init(
mikrotik_router.DOMAIN, context={"source": "user"}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=user_input
)
assert result["type"] == "form"
assert result["errors"] == {"base": "name_exists"}
async def test_connection_error(hass, conn_error):
"""Test error when connection is unsuccessful."""
result = await hass.config_entries.flow.async_init(
mikrotik_router.DOMAIN, context={"source": "user"}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=DEMO_USER_INPUT
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {"host": "cannot_connect"}
async def test_wrong_credentials(hass, auth_error):
"""Test error when credentials are wrong."""
result = await hass.config_entries.flow.async_init(
mikrotik_router.DOMAIN, context={"source": "user"}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=DEMO_USER_INPUT
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] == {"host": "cannot_connect"}