[mikrotik] Mikrotik RouterOS Binding - Initial contribution (#10014)

* [mikrotik] Initial contribution
Build fix
Linter concerns fixed
Post-review updates
Apply suggestions from code review

Co-authored-by: Matthew Skinner <matt@pcmus.com>
[mikrotik] Byte channels UOM update
[mikrotik] UOM updates; minor improvements
[mikrotik] ThingTypes update

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Version bump, bugfix (thanks @radokristof)

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Raw uptime channel removed

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Traces removed

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Readme update

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] More Null checks

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] thing-types update

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Units update

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Readme signal update

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Rate channels unit fix

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Work on codestyle report and some compiler warnings

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] No more compiler warnings

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

* [mikrotik] Minus null check

Signed-off-by: Oleg Vivtash <oleg@vivtash.net>

Co-authored-by: Fabian Wolter <github@fabian-wolter.de>

Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
Oleg Vivtash 2021-09-21 20:40:57 +03:00 committed by GitHub
parent fd646a59bd
commit 5ae1567ba8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 3958 additions and 0 deletions

View File

@ -172,6 +172,7 @@
/bundles/org.openhab.binding.mielecloud/ @BjoernLange /bundles/org.openhab.binding.mielecloud/ @BjoernLange
/bundles/org.openhab.binding.mihome/ @pboos /bundles/org.openhab.binding.mihome/ @pboos
/bundles/org.openhab.binding.miio/ @marcelrv /bundles/org.openhab.binding.miio/ @marcelrv
/bundles/org.openhab.binding.mikrotik/ @duhast
/bundles/org.openhab.binding.milight/ @davidgraeff /bundles/org.openhab.binding.milight/ @davidgraeff
/bundles/org.openhab.binding.millheat/ @seime /bundles/org.openhab.binding.millheat/ @seime
/bundles/org.openhab.binding.minecraft/ @ibaton /bundles/org.openhab.binding.minecraft/ @ibaton

View File

@ -848,6 +848,11 @@
<artifactId>org.openhab.binding.miio</artifactId> <artifactId>org.openhab.binding.miio</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.mikrotik</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.openhab.addons.bundles</groupId> <groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.milight</artifactId> <artifactId>org.openhab.binding.milight</artifactId>

View File

@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@ -0,0 +1,374 @@
# Mikrotik RouterOS Binding
This binding integrates [Mikrotik](https://mikrotik.com/) [RouterOS](https://help.mikrotik.com/docs/display/ROS/RouterOS)
[devices](https://mikrotik.com/products) allowing monitoring of system resources, network interfaces and WiFi clients.
## Supported Things
* `routeros` - An instance of the RouterOS device connection
* `interface` - A network interface inside RouterOS device
* `wifiRegistration` - Any wireless client connected to a RouterOS wireless network (regular or CAPsMAN-managed)
## Discovery
Discovery is currently not supported, but may be implemented in future versions.
## Bridge Configuration
To use this binding you need at least one RouterOS-powered device (Bridge) accessible to the host running
openHAB via network.
Make sure your RouterOS has the API enabled by visiting [<kbd>IP -> Services</kbd>](https://wiki.mikrotik.com/wiki/Manual:IP/Services)
configuration section in
[WinBox](https://wiki.mikrotik.com/wiki/Manual:Winbox).
Take note of the API port number as you'll need it below.
[SSL API connection](https://wiki.mikrotik.com/wiki/Manual:API-SSL) is not yet supported by this binding.
To connect to the RouterOS API, you will need to provide user credentials for the bridge thing.
You may use your current credentials that you use to manage your devices, but it is highly recommended to **create a read-only RouterOS user** since this binding only need to read data from the device.
To do this, proceed to <kbd>System -> Users</kbd> configuration section and add a user to the `read` group.
> Thing type: `routeros`
The RouterOS Bridge configuration parameters are:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| host | text | Yes | 192.168.88.1 | Hostname or IP address of the RouterOS device |
| port | integer | No | 8728 | API Port number of the RouterOS device |
| login | text | Yes | admin | The username to access the the RouterOS device |
| password | text | Yes | | The user password to access the RouterOS device |
| refresh | integer | No | 10 | The refresh interval in seconds to poll the RouterOS device |
**All things provided by this binding require a working bridge to be set up.**
### Bridge Channels
| Channel | Type | Description | Comment |
|---|---|---|---|
| freeSpace | Number:DataAmount | Amount of free storage left on device in bytes | |
| totalSpace | Number:DataAmount | Amount of total storage available on device in bytes | |
| usedSpace | Number:Dimensionless | Percentage of used device storage space | |
| freeMemory | Number:DataAmount | Amount of free memory left on device in bytes | |
| totalMemory | Number:DataAmount | Amount of total memory available on device in bytes | |
| usedMemory | Number:Dimensionless | Percentage of used device memory | |
| cpuLoad | Number:Dimensionless | CPU load percentage | |
| upSince | DateTime | Time when thing got up | |
## WiFi Client Thing Configuration
> Thing type: `wifiRegistration`
Represents a wireless client connected to a RouterOS wireless network (direct or CAPsMAN-managed).
The WiFi client thing configuration parameters are:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| mac | text | Yes | | WiFi client MAC address |
| ssid | text | No | | Constraining SSID for the WiFi client (optional). If client will connect to another SSID, this thing will stay offline until client reconnects to specified SSID. |
| considerContinuous | integer | No | 180 | The interval in seconds to treat the client as connected permanently |
### WiFi client Thing Channels
| Channel | Type | Description | Comment |
|---|---|---|---|
| macAddress | String | MAC address of the client or interface | |
| comment | String | User-defined comment | |
| connected | Switch | Reflects connected or disconnected state | |
| continuous | Switch | Connection is considered long-running | |
| ssid | String | Wireless Network (SSID) the wireless client is connected to | |
| interface | String | Network interface name | |
| signal | system.signal-strength | Signal strength (RSSI) | |
| upSince | DateTime | Time when thing got up | |
| lastSeen | DateTime | Time of when the client was last seen connected | |
| txRate | Number:DataTransferRate | Rate of data transmission in megabits per second | |
| rxRate | Number:DataTransferRate | Rate of data receiving in megabits per second | |
| txPacketRate | Number | Rate of data transmission in packets per second | |
| rxPacketRate | Number | Rate of data receiving in packets per second | |
| txBytes | Number:DataAmount | Amount of bytes transmitted | |
| rxBytes | Number:DataAmount | Amount of bytes received | |
| txPackets | Number | Amount of packets transmitted | |
| rxPackets | Number | Amount of packets received | |
## Network Interface Thing Configuration
> Thing type: `interface`
Represents a network interface from RouterOS system (ethernet, wifi, vpn, etc.)
At the moment the binding supports the following RouterOS interface types:
* `ether`
* `bridge`
* `wlan`
* `cap`
* `pppoe-out`
* `l2tp-in`
* `l2tp-out`
The interface thing configuration parameters are:
### Interface Thing Configuration
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| name | text | Yes | | RouterOS Interface name (i.e. ether1) |
### Interface Thing Channels
Please note that different on RouterOS interfaces has different data available depending on the kind of interface.
While the common dataset is same, some specific information for specific interface type may be missing. This may
be improved in future binding versions.
Common for all kinds of interfaces:
| Channel | Type | Description | Comment |
|---|---|---|---|
| type | String | Network interface type | |
| name | String | Network interface name | |
| comment | String | User-defined comment | |
| macAddress | String | MAC address of the client or interface | |
| enabled | Switch | Reflects enabled or disabled state | |
| connected | Switch | Reflects connected or disconnected state | |
| lastLinkDownTime | DateTime | Last time when link went down | |
| lastLinkUpTime | DateTime | Last time when link went up | |
| linkDowns | Number | Amount of link downs | |
| txRate | Number:DataTransferRate | Rate of data transmission in megabits per second | |
| rxRate | Number:DataTransferRate | Rate of data receiving in megabits per second | |
| txPacketRate | Number | Rate of data transmission in packets per second | |
| rxPacketRate | Number | Rate of data receiving in packets per second | |
| txBytes | Number:DataAmount | Amount of bytes transmitted | |
| rxBytes | Number:DataAmount | Amount of bytes received | |
| txPackets | Number | Amount of packets transmitted | |
| rxPackets | Number | Amount of packets received | |
| txDrops | Number | Amount of packets dropped during transmission | |
| rxDrops | Number | Amount of packets dropped during receiving | |
| txErrors | Number | Amount of errors during transmission | |
| rxErrors | Number | Amount of errors during receiving | |
| defaultName | String | Interface factory name | Populated only for `ether` interfaces |
| rate | String | Ethernet link rate | Populated only for `ether` interfaces |
| state | String | WiFi interface state | |
| registeredClients | Number | Amount of clients registered to WiFi interface | Populated only for `cap` interfaces |
| authorizedClients | Number | Amount of clients authorized by WiFi interface | Populated only for `cap` interfaces |
| upSince | DateTime | Time when thing got up | Populated only for `cap` interfaces |
## Text Configuration Example
**Change config options accordingly.**
_things/mikrotik.things_
```
Bridge mikrotik:routeros:rb1 "My RouterBoard" [ host="192.168.0.1", port=8728, login="openhab", password="thatsasecret", refresh=10 ] {
Thing interface eth1 "Eth1" [ name="ether1" ]
Thing interface eth2 "Eth2" [ name="ether2-wan1" ]
Thing interface cap1 "Cap1" [ name="cap5" ]
Thing interface ppp1 "PPPoE1" [ name="isp-pppoe" ]
Thing interface tun1 "L2TPSrv1" [ name="l2tp-parents" ]
Thing wifiRegistration wifi1 "Phone1" [ mac="F4:60:E2:C5:47:94", considerContinuous=60 ]
Thing wifiRegistration wifi2 "Tablet2" [ mac="18:1D:EA:A5:A2:9E" ]
}
```
_items/mikrotik.items_
```
Group gRB1 "RB3011 System"
Number:DataAmount My_RB_3011_Free_Space "Free space" (gRB1) {channel="mikrotik:routeros:rb1:freeSpace"}
Number:DataAmount My_RB_3011_Total_Space "Total space" (gRB1) {channel="mikrotik:routeros:rb1:totalSpace"}
Number:Dimensionless My_RB_3011_Used_Space "Used space" (gRB1) {channel="mikrotik:routeros:rb1:usedSpace"}
Number:DataAmount My_RB_3011_Free_Memory "Free ram" (gRB1) {channel="mikrotik:routeros:rb1:freeMemory"}
Number:DataAmount My_RB_3011_Total_Memory "Total ram" (gRB1) {channel="mikrotik:routeros:rb1:totalMemory"}
Number:Dimensionless My_RB_3011_Used_Memory "Used ram" (gRB1) {channel="mikrotik:routeros:rb1:usedMemory"}
Number:Dimensionless My_RB_3011_Cpu_Load "Cpu load" (gRB1) {channel="mikrotik:routeros:rb1:cpuLoad"}
DateTime My_RB_3011_Upsince "Up since [%1$td.%1$tm.%1$ty %1$tH:%1$tM]" (gRB1) {channel="mikrotik:routeros:rb1:upSince"}
Group gRB1Eth1 "Ethernet Interface 1"
String Eth_1_Type "Type" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:type"}
String Eth_1_Name "Name" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:name"}
String Eth_1_Comment "Comment" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:comment"}
String Eth_1_Mac_Address "Mac address" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:macAddress"}
Switch Eth_1_Enabled "Enabled" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:enabled"}
Switch Eth_1_Connected "Connected" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:connected"}
DateTime Eth_1_Last_Link_Down_Time "Last link down" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:lastLinkDownTime"}
DateTime Eth_1_Last_Link_Up_Time "Last link up" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:lastLinkUpTime"}
Number Eth_1_Link_Downs "Link downs" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:linkDowns"}
Number:DataTransferRate Eth_1_Tx_Rate "Transmission rate" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:txRate"}
Number:DataTransferRate Eth_1_Rx_Rate "Receiving rate" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:rxRate"}
Number Eth_1_Tx_Packet_Rate "Transmission packet rate" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:txPacketRate"}
Number Eth_1_Rx_Packet_Rate "Receiving packet rate" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:rxPacketRate"}
Number:DataAmount Eth_1_Tx_Bytes "Transmitted bytes" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:txBytes"}
Number:DataAmount Eth_1_Rx_Bytes "Received bytes" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:rxBytes"}
Number Eth_1_Tx_Packets "Transmitted packets" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:txPackets"}
Number Eth_1_Rx_Packets "Received packets" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:rxPackets"}
Number Eth_1_Tx_Drops "Transmission drops" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:txDrops"}
Number Eth_1_Rx_Drops "Receiving drops" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:rxDrops"}
Number Eth_1_Tx_Errors "Transmission errors" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:txErrors"}
Number Eth_1_Rx_Errors "Receiving errors" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:rxErrors"}
String Eth_1_Default_Name "Default name" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:defaultName"}
String Eth_1_Rate "Link rate" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:rate"}
String Eth_1_Auto_Negotiation "Auto negotiation" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:autoNegotiation"}
String Eth_1_State "State" (gRB1Eth1) {channel="mikrotik:interface:rb1:eth1:state"}
Group gRB1Eth2 "Ethernet Interface 2"
String Eth_2_Type "Type" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:type"}
String Eth_2_Name "Name" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:name"}
String Eth_2_Comment "Comment" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:comment"}
String Eth_2_Mac_Address "Mac address" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:macAddress"}
Switch Eth_2_Enabled "Enabled" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:enabled"}
Switch Eth_2_Connected "Connected" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:connected"}
DateTime Eth_2_Last_Link_Down_Time "Last link down" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:lastLinkDownTime"}
DateTime Eth_2_Last_Link_Up_Time "Last link up" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:lastLinkUpTime"}
Number Eth_2_Link_Downs "Link downs" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:linkDowns"}
Number:DataTransferRate Eth_2_Tx_Rate "Transmission rate" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:txRate"}
Number:DataTransferRate Eth_2_Rx_Rate "Receiving rate" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:rxRate"}
Number Eth_2_Tx_Packet_Rate "Transmission packet rate" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:txPacketRate"}
Number Eth_2_Rx_Packet_Rate "Receiving packet rate" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:rxPacketRate"}
Number:DataAmount Eth_2_Tx_Bytes "Transmitted bytes" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:txBytes"}
Number:DataAmount Eth_2_Rx_Bytes "Received bytes" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:rxBytes"}
Number Eth_2_Tx_Packets "Transmitted packets" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:txPackets"}
Number Eth_2_Rx_Packets "Received packets" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:rxPackets"}
Number Eth_2_Tx_Drops "Transmission drops" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:txDrops"}
Number Eth_2_Rx_Drops "Receiving drops" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:rxDrops"}
Number Eth_2_Tx_Errors "Transmission errors" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:txErrors"}
Number Eth_2_Rx_Errors "Receiving errors" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:rxErrors"}
String Eth_2_Default_Name "Default name" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:defaultName"}
String Eth_2_Rate "Link rate" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:rate"}
String Eth_2_Auto_Negotiation "Auto negotiation" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:autoNegotiation"}
String Eth_2_State "State" (gRB1Eth2) {channel="mikrotik:interface:rb1:eth2:state"}
Group gRB1Cap1 "CAPsMAN Inerface 1"
String Cap_1_Type "Type" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:type"}
String Cap_1_Name "Name" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:name"}
String Cap_1_Comment "Comment" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:comment"}
String Cap_1_Mac_Address "Mac address" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:macAddress"}
Switch Cap_1_Enabled "Enabled" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:enabled"}
Switch Cap_1_Connected "Connected" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:connected"}
DateTime Cap_1_Last_Link_Down_Time "Last link down" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:lastLinkDownTime"}
DateTime Cap_1_Last_Link_Up_Time "Last link up" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:lastLinkUpTime"}
Number Cap_1_Link_Downs "Link downs" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:linkDowns"}
Number:DataTransferRate Cap_1_Tx_Rate "Transmission rate" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:txRate"}
Number:DataTransferRate Cap_1_Rx_Rate "Receiving rate" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:rxRate"}
Number Cap_1_Tx_Packet_Rate "Transmission packet rate" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:txPacketRate"}
Number Cap_1_Rx_Packet_Rate "Receiving packet rate" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:rxPacketRate"}
Number:DataAmount Cap_1_Tx_Bytes "Transmitted bytes" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:txBytes"}
Number:DataAmount Cap_1_Rx_Bytes "Received bytes" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:rxBytes"}
Number Cap_1_Tx_Packets "Transmitted packets" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:txPackets"}
Number Cap_1_Rx_Packets "Received packets" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:rxPackets"}
Number Cap_1_Tx_Drops "Transmission drops" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:txDrops"}
Number Cap_1_Rx_Drops "Receiving drops" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:rxDrops"}
Number Cap_1_Tx_Errors "Transmission errors" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:txErrors"}
Number Cap_1_Rx_Errors "Receiving errors" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:rxErrors"}
String Cap_1_State "State" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:state"}
Number Cap_1_Registered_Clients "Registered clients" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:registeredClients"}
Number Cap_1_Authorized_Clients "Authorized clients" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:authorizedClients"}
DateTime Cap_1_Up_Since "Up since" (gRB1Cap1) {channel="mikrotik:interface:rb1:cap1:upSince"}
Group gRB1Ppp1 "PPPoE Client 1"
String PP_Po_E_1_Type "Type" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:type"}
String PP_Po_E_1_Name "Name" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:name"}
String PP_Po_E_1_Comment "Comment" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:comment"}
String PP_Po_E_1_Mac_Address "Mac address" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:macAddress"}
Switch PP_Po_E_1_Enabled "Enabled" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:enabled"}
Switch PP_Po_E_1_Connected "Connected" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:connected"}
DateTime PP_Po_E_1_Last_Link_Down_Time "Last link down" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:lastLinkDownTime"}
DateTime PP_Po_E_1_Last_Link_Up_Time "Last link up" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:lastLinkUpTime"}
Number PP_Po_E_1_Link_Downs "Link downs" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:linkDowns"}
Number:DataTransferRate PP_Po_E_1_Tx_Rate "Transmission rate" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:txRate"}
Number:DataTransferRate PP_Po_E_1_Rx_Rate "Receiving rate" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:rxRate"}
Number PP_Po_E_1_Tx_Packet_Rate "Transmission packet rate" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:txPacketRate"}
Number PP_Po_E_1_Rx_Packet_Rate "Receiving packet rate" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:rxPacketRate"}
Number:DataAmount PP_Po_E_1_Tx_Bytes "Transmitted bytes" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:txBytes"}
Number:DataAmount PP_Po_E_1_Rx_Bytes "Received bytes" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:rxBytes"}
Number PP_Po_E_1_Tx_Packets "Transmitted packets" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:txPackets"}
Number PP_Po_E_1_Rx_Packets "Received packets" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:rxPackets"}
Number PP_Po_E_1_Tx_Drops "Transmission drops" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:txDrops"}
Number PP_Po_E_1_Rx_Drops "Receiving drops" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:rxDrops"}
Number PP_Po_E_1_Tx_Errors "Transmission errors" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:txErrors"}
Number PP_Po_E_1_Rx_Errors "Receiving errors" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:rxErrors"}
String PP_Po_E_1_State "State" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:state"}
DateTime PP_Po_E_1_Up_Since "Up since" (gRB1Ppp1) {channel="mikrotik:interface:rb1:ppp1:upSince"}
Group gRB1Tun1 "L2TP Server 1"
String L_2_TP_Srv_1_Type "Type" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:type"}
String L_2_TP_Srv_1_Name "Name" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:name"}
String L_2_TP_Srv_1_Comment "Comment" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:comment"}
String L_2_TP_Srv_1_Mac_Address "Mac address" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:macAddress"}
Switch L_2_TP_Srv_1_Enabled "Enabled" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:enabled"}
Switch L_2_TP_Srv_1_Connected "Connected" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:connected"}
DateTime L_2_TP_Srv_1_Last_Link_Down_Time "Last link down" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:lastLinkDownTime"}
DateTime L_2_TP_Srv_1_Last_Link_Up_Time "Last link up" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:lastLinkUpTime"}
Number L_2_TP_Srv_1_Link_Downs "Link downs" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:linkDowns"}
Number:DataTransferRate L_2_TP_Srv_1_Tx_Rate "Transmission rate" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:txRate"}
Number:DataTransferRate L_2_TP_Srv_1_Rx_Rate "Receiving rate" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:rxRate"}
Number L_2_TP_Srv_1_Tx_Packet_Rate "Transmission packet rate" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:txPacketRate"}
Number L_2_TP_Srv_1_Rx_Packet_Rate "Receiving packet rate" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:rxPacketRate"}
Number:DataAmount L_2_TP_Srv_1_Tx_Bytes "Transmitted bytes" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:txBytes"}
Number:DataAmount L_2_TP_Srv_1_Rx_Bytes "Received bytes" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:rxBytes"}
Number L_2_TP_Srv_1_Tx_Packets "Transmitted packets" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:txPackets"}
Number L_2_TP_Srv_1_Rx_Packets "Received packets" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:rxPackets"}
Number L_2_TP_Srv_1_Tx_Drops "Transmission drops" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:txDrops"}
Number L_2_TP_Srv_1_Rx_Drops "Receiving drops" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:rxDrops"}
Number L_2_TP_Srv_1_Tx_Errors "Transmission errors" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:txErrors"}
Number L_2_TP_Srv_1_Rx_Errors "Receiving errors" (gRB1Tun1) {channel="mikrotik:interface:rb1:tun1:rxErrors"}
Group gRB1Wifi1 "WiFi Client 1"
String Phone_1_Mac_Address "Mac address" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:macAddress"}
String Phone_1_Comment "Comment" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:comment"}
Switch Phone_1_Connected "Connected" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:connected"}
Switch Phone_1_Continuous "Continuous" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:continuous"}
String Phone_1_Ssid "Wi fi network" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:ssid"}
String Phone_1_Interface "Name" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:interface"}
Number Phone_1_Signal "Received signal strength indicator" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:signal"}
DateTime Phone_1_Up_Since "Up since" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:upSince"}
DateTime Phone_1_Last_Seen "Last seen" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:lastSeen"}
Number:DataTransferRate Phone_1_Tx_Rate "Transmission rate" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:txRate"}
Number:DataTransferRate Phone_1_Rx_Rate "Receiving rate" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:rxRate"}
Number Phone_1_Tx_Packet_Rate "Transmission packet rate" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:txPacketRate"}
Number Phone_1_Rx_Packet_Rate "Receiving packet rate" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:rxPacketRate"}
Number:DataAmount Phone_1_Tx_Bytes "Transmitted bytes" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:txBytes"}
Number:DataAmount Phone_1_Rx_Bytes "Received bytes" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:rxBytes"}
Number Phone_1_Tx_Packets "Transmitted packets" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:txPackets"}
Number Phone_1_Rx_Packets "Received packets" (gRB1Wifi1) {channel="mikrotik:wifiRegistration:rb1:wifi1:rxPackets"}
Group gRB1Wifi2 "WiFi Client 2"
String Tablet_2_Mac_Address "Mac address" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:macAddress"}
String Tablet_2_Comment "Comment" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:comment"}
Switch Tablet_2_Connected "Connected" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:connected"}
Switch Tablet_2_Continuous "Continuous" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:continuous"}
String Tablet_2_Ssid "Wi fi network" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:ssid"}
String Tablet_2_Interface "Name" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:interface"}
Number Tablet_2_Signal "Received signal strength indicator" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:signal"}
DateTime Tablet_2_Up_Since "Up since" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:upSince"}
DateTime Tablet_2_Last_Seen "Last seen" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:lastSeen"}
Number:DataTransferRate Tablet_2_Tx_Rate "Transmission rate" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:txRate"}
Number:DataTransferRate Tablet_2_Rx_Rate "Receiving rate" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:rxRate"}
Number Tablet_2_Tx_Packet_Rate "Transmission packet rate" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:txPacketRate"}
Number Tablet_2_Rx_Packet_Rate "Receiving packet rate" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:rxPacketRate"}
Number:DataAmount Tablet_2_Tx_Bytes "Transmitted bytes" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:txBytes"}
Number:DataAmount Tablet_2_Rx_Bytes "Received bytes" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:rxBytes"}
Number Tablet_2_Tx_Packets "Transmitted packets" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:txPackets"}
Number Tablet_2_Rx_Packets "Received packets" (gRB1Wifi2) {channel="mikrotik:wifiRegistration:rb1:wifi2:rxPackets"}
```
_sitemaps/mikrotik.sitemap_
```
sitemap mikrotik label="Mikrotik Binding Demo"
{
Frame label="RouterBOARD 1" {
Group item=gRB1
Group item=gRB1Eth1
Group item=gRB1Eth2
Group item=gRB1Ppp1
Group item=gRB1Tun1
Group item=gRB1Cap1
Group item=gRB1Wifi1
Group item=gRB1Wifi2
}
}
```

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.2.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.mikrotik</artifactId>
<name>openHAB Add-ons :: Bundles :: Mikrotik Binding</name>
<dependencies>
<dependency>
<groupId>me.legrange</groupId>
<artifactId>mikrotik</artifactId>
<version>3.0.7</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.mikrotik-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-mikrotik" description="Mikrotik Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.mikrotik/${project.version}</bundle>
</feature>
</features>

View File

@ -0,0 +1,95 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link MikrotikBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class MikrotikBindingConstants {
private static final String BINDING_ID = "mikrotik";
public static final String PROPERTY_MODEL = "modelId";
public static final String PROPERTY_FIRMWARE = "firmware";
public static final String PROPERTY_SERIAL_NUMBER = "serial";
// List of all Thing Types
public static final ThingTypeUID THING_TYPE_ROUTEROS = new ThingTypeUID(BINDING_ID, "routeros");
public static final ThingTypeUID THING_TYPE_INTERFACE = new ThingTypeUID(BINDING_ID, "interface");
public static final ThingTypeUID THING_TYPE_WIRELESS_CLIENT = new ThingTypeUID(BINDING_ID, "wifiRegistration");
// RouterOS system stats
public static final String CHANNEL_FREE_SPACE = "freeSpace";
public static final String CHANNEL_TOTAL_SPACE = "totalSpace";
public static final String CHANNEL_USED_SPACE = "usedSpace";
public static final String CHANNEL_FREE_MEM = "freeMemory";
public static final String CHANNEL_TOTAL_MEM = "totalMemory";
public static final String CHANNEL_USED_MEM = "usedMemory";
public static final String CHANNEL_CPU_LOAD = "cpuLoad";
public static final String CHANNEL_COMMENT = "comment";
// List of common interface channels
public static final String CHANNEL_NAME = "name";
public static final String CHANNEL_TYPE = "type";
public static final String CHANNEL_MAC = "macAddress";
public static final String CHANNEL_ENABLED = "enabled";
public static final String CHANNEL_CONNECTED = "connected"; // used for wifi client as well
public static final String CHANNEL_LAST_LINK_DOWN_TIME = "lastLinkDownTime";
public static final String CHANNEL_LAST_LINK_UP_TIME = "lastLinkUpTime";
public static final String CHANNEL_LINK_DOWNS = "linkDowns";
public static final String CHANNEL_TX_DATA_RATE = "txRate";
public static final String CHANNEL_RX_DATA_RATE = "rxRate";
public static final String CHANNEL_TX_PACKET_RATE = "txPacketRate";
public static final String CHANNEL_RX_PACKET_RATE = "rxPacketRate";
public static final String CHANNEL_TX_BYTES = "txBytes";
public static final String CHANNEL_RX_BYTES = "rxBytes";
public static final String CHANNEL_TX_PACKETS = "txPackets";
public static final String CHANNEL_RX_PACKETS = "rxPackets";
public static final String CHANNEL_TX_DROPS = "txDrops";
public static final String CHANNEL_RX_DROPS = "rxDrops";
public static final String CHANNEL_TX_ERRORS = "txErrors";
public static final String CHANNEL_RX_ERRORS = "rxErrors";
// Ethernet interface channel list
public static final String CHANNEL_DEFAULT_NAME = "defaultName";
public static final String CHANNEL_RATE = "rate";
// CAPsMAN interface channel list
public static final String CHANNEL_INTERFACE = "interface";
public static final String CHANNEL_STATE = "state";
public static final String CHANNEL_REGISTERED_CLIENTS = "registeredClients";
public static final String CHANNEL_AUTHORIZED_CLIENTS = "authorizedClients";
public static final String CHANNEL_CONTINUOUS = "continuous";
// PPP interface shared channel list
public static final String CHANNEL_UP_SINCE = "upSince";
// Wireless client channels
public static final String CHANNEL_LAST_SEEN = "lastSeen";
public static final String CHANNEL_SSID = "ssid";
public static final String CHANNEL_SIGNAL = "signal";
// List of common wired + wireless client channels
public static final String CHANNEL_SITE = "site";
public static final String CHANNEL_IP_ADDRESS = "ipAddress";
public static final String CHANNEL_BLOCKED = "blocked";
public static final String CHANNEL_RECONNECT = "reconnect";
}

View File

@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.handler.MikrotikInterfaceThingHandler;
import org.openhab.binding.mikrotik.internal.handler.MikrotikRouterosBridgeHandler;
import org.openhab.binding.mikrotik.internal.handler.MikrotikWirelessClientThingHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link MikrotikHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.mikrotik", service = ThingHandlerFactory.class)
public class MikrotikHandlerFactory extends BaseThingHandlerFactory {
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return MikrotikRouterosBridgeHandler.supportsThingType(thingTypeUID)
|| MikrotikWirelessClientThingHandler.supportsThingType(thingTypeUID)
|| MikrotikInterfaceThingHandler.supportsThingType(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (MikrotikRouterosBridgeHandler.supportsThingType(thingTypeUID)) {
return new MikrotikRouterosBridgeHandler((Bridge) thing);
} else if (MikrotikWirelessClientThingHandler.supportsThingType(thingTypeUID)) {
return new MikrotikWirelessClientThingHandler(thing);
} else if (MikrotikInterfaceThingHandler.supportsThingType(thingTypeUID)) {
return new MikrotikInterfaceThingHandler(thing);
}
return null;
}
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ConfigValidation} interface should be implemented by all config objects, so the thing handlers could
* change their state properly, based on the config validation result.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public interface ConfigValidation {
boolean isValid();
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link InterfaceThingConfig} class contains fields mapping thing configuration parameters for
* network interface things.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class InterfaceThingConfig implements ConfigValidation {
public String name = "";
public boolean isValid() {
return !name.isBlank();
}
@Override
public String toString() {
return String.format("InterfaceThingConfig{name=%s}", name);
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link RouterosThingConfig} class contains fields mapping thing configuration parameters for
* RouterOS bridge thing.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosThingConfig implements ConfigValidation {
public String host = "rb3011";
public int port = 8728;
public String login = "admin";
public String password = "";
public int refresh = 10;
public boolean isValid() {
return !host.isBlank() && !login.isBlank() && !password.isBlank() && refresh > 0 && port > 0;
}
@Override
public String toString() {
return String.format("RouterosThingConfig{host=%s, port=%d, login=%s, password=*****, refresh=%ds}", host, port,
login, refresh);
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link WirelessClientThingConfig} class contains fields mapping thing configuration parameters for
* WiFi client thing.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class WirelessClientThingConfig implements ConfigValidation {
public String mac = "";
public String ssid = "";
public int considerContinuous = 180;
public boolean isValid() {
return !mac.isBlank() && considerContinuous > 0;
}
@Override
public String toString() {
return String.format("WirelessClientThingConfig{mac=%s, ssid=%s, considerContinuous=%ds}", mac, ssid,
considerContinuous);
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
/**
* The {@link ChannelUpdateException} is used to bubble up channel update errors which are mainly
* happens during data conversion. But those errors should not bring bridge offline and break normal
* operation.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class ChannelUpdateException extends RuntimeException {
static final long serialVersionUID = 1L;
private final ThingUID thingUID;
private final ChannelUID channelID;
public ChannelUpdateException(ThingUID thingUID, ChannelUID channelUID, Throwable cause) {
super(cause);
this.thingUID = thingUID;
this.channelID = channelUID;
}
@Override
public @Nullable String getMessage() {
return String.format("%s @ %s/%s", super.getMessage(), thingUID, channelID);
}
}

View File

@ -0,0 +1,246 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.handler;
import static org.openhab.core.thing.ThingStatus.OFFLINE;
import static org.openhab.core.thing.ThingStatus.ONLINE;
import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.lang.reflect.ParameterizedType;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.config.ConfigValidation;
import org.openhab.binding.mikrotik.internal.config.RouterosThingConfig;
import org.openhab.binding.mikrotik.internal.model.RouterosDevice;
import org.openhab.core.cache.ExpiringCache;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MikrotikBaseThingHandler} is a base class for all other RouterOS things of map-value nature.
* It is responsible for handling commands, which are sent to one of the channels and emit channel updates
* whenever required.
*
* @author Oleg Vivtash - Initial contribution
*
*
* @param <C> config - the config class used by this base thing handler
*
*/
@NonNullByDefault
public abstract class MikrotikBaseThingHandler<C extends ConfigValidation> extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(MikrotikBaseThingHandler.class);
protected @Nullable C config;
private @Nullable ScheduledFuture<?> refreshJob;
protected ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofDays(1), () -> false);
protected Map<String, State> currentState = new HashMap<>();
// public static boolean supportsThingType(ThingTypeUID thingTypeUID) <- in subclasses
public MikrotikBaseThingHandler(Thing thing) {
super(thing);
}
protected @Nullable MikrotikRouterosBridgeHandler getVerifiedBridgeHandler() {
Bridge bridgeRef = getBridge();
if (bridgeRef != null && bridgeRef.getHandler() != null
&& (bridgeRef.getHandler() instanceof MikrotikRouterosBridgeHandler)) {
return (MikrotikRouterosBridgeHandler) bridgeRef.getHandler();
}
return null;
}
protected final @Nullable RouterosDevice getRouterOs() {
MikrotikRouterosBridgeHandler bridgeHandler = getVerifiedBridgeHandler();
return bridgeHandler == null ? null : bridgeHandler.getRouteros();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Handling command = {} for channel = {}", command, channelUID);
if (getThing().getStatus() == ONLINE) {
RouterosDevice routeros = getRouterOs();
if (routeros != null) {
if (command == REFRESH) {
refreshCache.getValue();
refreshChannel(channelUID);
} else {
try {
executeCommand(channelUID, command);
} catch (RuntimeException e) {
logger.warn("Unexpected error handling command = {} for channel = {} : {}", command, channelUID,
e.getMessage());
}
}
}
}
}
@SuppressWarnings("unchecked")
@Override
public void initialize() {
cancelRefreshJob();
if (getVerifiedBridgeHandler() == null) {
updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "This thing requires a RouterOS bridge");
return;
}
var superKlass = (ParameterizedType) getClass().getGenericSuperclass();
if (superKlass == null) {
updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"getGenericSuperclass failed for thing handler");
return;
}
Class<?> klass = (Class<?>) (superKlass.getActualTypeArguments()[0]);
C localConfig = (C) getConfigAs(klass);
this.config = localConfig;
if (!localConfig.isValid()) {
updateStatus(OFFLINE, CONFIGURATION_ERROR, String.format("%s is invalid", klass.getSimpleName()));
return;
}
updateStatus(ONLINE);
}
@Override
protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
if (status == ONLINE || (status == OFFLINE && statusDetail == ThingStatusDetail.COMMUNICATION_ERROR)) {
scheduleRefreshJob();
} else if (status == OFFLINE
&& (statusDetail == ThingStatusDetail.CONFIGURATION_ERROR || statusDetail == ThingStatusDetail.GONE)) {
cancelRefreshJob();
}
// update the status only if it's changed
ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail).withDescription(description)
.build();
if (!statusInfo.equals(getThing().getStatusInfo())) {
super.updateStatus(status, statusDetail, description);
}
}
@SuppressWarnings("null")
private void scheduleRefreshJob() {
synchronized (this) {
if (refreshJob == null) {
var bridgeHandler = getVerifiedBridgeHandler();
if (bridgeHandler == null) {
updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Cannot obtain bridge handler");
return;
}
RouterosThingConfig bridgeConfig = bridgeHandler.getBridgeConfig();
int refreshPeriod = bridgeConfig.refresh;
logger.debug("Scheduling refresh job every {}s", refreshPeriod);
this.refreshCache = new ExpiringCache<>(Duration.ofSeconds(refreshPeriod), this::verifiedRefreshModels);
refreshJob = scheduler.scheduleWithFixedDelay(this::scheduledRun, refreshPeriod, refreshPeriod,
TimeUnit.SECONDS);
}
}
}
private void cancelRefreshJob() {
synchronized (this) {
var job = this.refreshJob;
if (job != null) {
logger.debug("Cancelling refresh job");
job.cancel(true);
this.refreshJob = null;
// Not setting to null as getValue() can potentially be called after
this.refreshCache = new ExpiringCache<>(Duration.ofDays(1), () -> false);
}
}
}
private void scheduledRun() {
MikrotikRouterosBridgeHandler bridgeHandler = getVerifiedBridgeHandler();
if (bridgeHandler == null) {
updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Failed reaching out to RouterOS bridge");
return;
}
Bridge bridge = getBridge();
if (bridge != null && bridge.getStatus() == OFFLINE) {
updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "The RouterOS bridge is currently offline");
return;
}
if (getThing().getStatus() != ONLINE) {
updateStatus(ONLINE);
}
logger.debug("Refreshing all {} channels", getThing().getUID());
for (Channel channel : getThing().getChannels()) {
try {
refreshChannel(channel.getUID());
} catch (RuntimeException e) {
logger.warn("Unhandled exception while refreshing the {} Mikrotik thing", getThing().getUID(), e);
updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
protected final void refresh() throws ChannelUpdateException {
if (getThing().getStatus() == ONLINE) {
if (getRouterOs() != null) {
refreshCache.getValue();
for (Channel channel : getThing().getChannels()) {
ChannelUID channelUID = channel.getUID();
try {
refreshChannel(channelUID);
} catch (RuntimeException e) {
throw new ChannelUpdateException(getThing().getUID(), channelUID, e);
}
}
}
}
}
protected boolean verifiedRefreshModels() {
if (getRouterOs() != null && config != null) {
refreshModels();
return true;
} else {
return false;
}
}
@Override
public void dispose() {
cancelRefreshJob();
}
protected abstract void refreshModels();
protected abstract void refreshChannel(ChannelUID channelUID);
protected abstract void executeCommand(ChannelUID channelUID, Command command);
}

View File

@ -0,0 +1,316 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.handler;
import static org.openhab.core.thing.ThingStatus.OFFLINE;
import static org.openhab.core.thing.ThingStatus.ONLINE;
import static org.openhab.core.thing.ThingStatusDetail.GONE;
import java.math.BigDecimal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.MikrotikBindingConstants;
import org.openhab.binding.mikrotik.internal.config.InterfaceThingConfig;
import org.openhab.binding.mikrotik.internal.model.RouterosCapInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosDevice;
import org.openhab.binding.mikrotik.internal.model.RouterosEthernetInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosInterfaceBase;
import org.openhab.binding.mikrotik.internal.model.RouterosL2TPCliInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosL2TPSrvInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosPPPoECliInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosWlanInterface;
import org.openhab.binding.mikrotik.internal.util.RateCalculator;
import org.openhab.binding.mikrotik.internal.util.StateUtil;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MikrotikInterfaceThingHandler} is a {@link MikrotikBaseThingHandler} subclass that wraps shared
* functionality for all interface things of different types. It is responsible for handling commands, which are
* sent to one of the channels and emit channel updates whenever required.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class MikrotikInterfaceThingHandler extends MikrotikBaseThingHandler<InterfaceThingConfig> {
private final Logger logger = LoggerFactory.getLogger(MikrotikInterfaceThingHandler.class);
private @Nullable RouterosInterfaceBase iface;
private final RateCalculator txByteRate = new RateCalculator(BigDecimal.ZERO);
private final RateCalculator rxByteRate = new RateCalculator(BigDecimal.ZERO);
private final RateCalculator txPacketRate = new RateCalculator(BigDecimal.ZERO);
private final RateCalculator rxPacketRate = new RateCalculator(BigDecimal.ZERO);
public static boolean supportsThingType(ThingTypeUID thingTypeUID) {
return MikrotikBindingConstants.THING_TYPE_INTERFACE.equals(thingTypeUID);
}
public MikrotikInterfaceThingHandler(Thing thing) {
super(thing);
}
@Override
protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
RouterosDevice routeros = getRouterOs();
InterfaceThingConfig cfg = this.config;
if (routeros != null && cfg != null) {
if (status == ONLINE || (status == OFFLINE && statusDetail == ThingStatusDetail.COMMUNICATION_ERROR)) {
routeros.registerForMonitoring(cfg.name);
} else if (status == OFFLINE
&& (statusDetail == ThingStatusDetail.CONFIGURATION_ERROR || statusDetail == GONE)) {
routeros.unregisterForMonitoring(cfg.name);
}
}
super.updateStatus(status, statusDetail, description);
}
@Override
protected void refreshModels() {
RouterosDevice routeros = getRouterOs();
InterfaceThingConfig cfg = this.config;
if (routeros != null && cfg != null) {
RouterosInterfaceBase rosInterface = routeros.findInterface(cfg.name);
this.iface = rosInterface;
if (rosInterface == null) {
String statusMsg = String.format("Interface %s is not found in RouterOS for thing %s", cfg.name,
getThing().getUID());
updateStatus(OFFLINE, GONE, statusMsg);
} else {
txByteRate.update(rosInterface.getTxBytes());
rxByteRate.update(rosInterface.getRxBytes());
txPacketRate.update(rosInterface.getTxPackets());
rxPacketRate.update(rosInterface.getRxPackets());
}
}
}
@Override
protected void refreshChannel(ChannelUID channelUID) {
String channelID = channelUID.getIdWithoutGroup();
State oldState = currentState.getOrDefault(channelID, UnDefType.NULL);
State newState = oldState;
RouterosInterfaceBase iface = this.iface;
if (iface == null) {
newState = UnDefType.NULL;
} else {
switch (channelID) {
case MikrotikBindingConstants.CHANNEL_NAME:
newState = StateUtil.stringOrNull(iface.getName());
break;
case MikrotikBindingConstants.CHANNEL_COMMENT:
newState = StateUtil.stringOrNull(iface.getComment());
break;
case MikrotikBindingConstants.CHANNEL_TYPE:
newState = StateUtil.stringOrNull(iface.getType());
break;
case MikrotikBindingConstants.CHANNEL_MAC:
newState = StateUtil.stringOrNull(iface.getMacAddress());
break;
case MikrotikBindingConstants.CHANNEL_ENABLED:
newState = StateUtil.boolOrNull(iface.isEnabled());
break;
case MikrotikBindingConstants.CHANNEL_CONNECTED:
newState = StateUtil.boolOrNull(iface.isConnected());
break;
case MikrotikBindingConstants.CHANNEL_LAST_LINK_DOWN_TIME:
newState = StateUtil.timeOrNull(iface.getLastLinkDownTime());
break;
case MikrotikBindingConstants.CHANNEL_LAST_LINK_UP_TIME:
newState = StateUtil.timeOrNull(iface.getLastLinkUpTime());
break;
case MikrotikBindingConstants.CHANNEL_LINK_DOWNS:
newState = StateUtil.intOrNull(iface.getLinkDowns());
break;
case MikrotikBindingConstants.CHANNEL_TX_DATA_RATE:
newState = StateUtil.qtyMegabitPerSecOrNull(txByteRate.getMegabitRate());
break;
case MikrotikBindingConstants.CHANNEL_RX_DATA_RATE:
newState = StateUtil.qtyMegabitPerSecOrNull(rxByteRate.getMegabitRate());
break;
case MikrotikBindingConstants.CHANNEL_TX_PACKET_RATE:
newState = StateUtil.floatOrNull(txPacketRate.getRate());
break;
case MikrotikBindingConstants.CHANNEL_RX_PACKET_RATE:
newState = StateUtil.floatOrNull(rxPacketRate.getRate());
break;
case MikrotikBindingConstants.CHANNEL_TX_BYTES:
newState = StateUtil.bigIntOrNull(iface.getTxBytes());
break;
case MikrotikBindingConstants.CHANNEL_RX_BYTES:
newState = StateUtil.bigIntOrNull(iface.getRxBytes());
break;
case MikrotikBindingConstants.CHANNEL_TX_PACKETS:
newState = StateUtil.bigIntOrNull(iface.getTxPackets());
break;
case MikrotikBindingConstants.CHANNEL_RX_PACKETS:
newState = StateUtil.bigIntOrNull(iface.getRxPackets());
break;
case MikrotikBindingConstants.CHANNEL_TX_DROPS:
newState = StateUtil.bigIntOrNull(iface.getTxDrops());
break;
case MikrotikBindingConstants.CHANNEL_RX_DROPS:
newState = StateUtil.bigIntOrNull(iface.getRxDrops());
break;
case MikrotikBindingConstants.CHANNEL_TX_ERRORS:
newState = StateUtil.bigIntOrNull(iface.getTxErrors());
break;
case MikrotikBindingConstants.CHANNEL_RX_ERRORS:
newState = StateUtil.bigIntOrNull(iface.getRxErrors());
break;
default:
if (iface instanceof RouterosEthernetInterface) {
newState = getEtherIterfaceChannelState(channelID);
} else if (iface instanceof RouterosCapInterface) {
newState = getCapIterfaceChannelState(channelID);
} else if (iface instanceof RouterosWlanInterface) {
newState = getWlanIterfaceChannelState(channelID);
} else if (iface instanceof RouterosPPPoECliInterface) {
newState = getPPPoECliChannelState(channelID);
} else if (iface instanceof RouterosL2TPSrvInterface) {
newState = getL2TPSrvChannelState(channelID);
} else if (iface instanceof RouterosL2TPCliInterface) {
newState = getL2TPCliChannelState(channelID);
}
}
}
if (!newState.equals(oldState)) {
updateState(channelID, newState);
currentState.put(channelID, newState);
}
}
protected State getEtherIterfaceChannelState(String channelID) {
RouterosEthernetInterface etherIface = (RouterosEthernetInterface) this.iface;
if (etherIface == null) {
return UnDefType.UNDEF;
}
switch (channelID) {
case MikrotikBindingConstants.CHANNEL_DEFAULT_NAME:
return StateUtil.stringOrNull(etherIface.getDefaultName());
case MikrotikBindingConstants.CHANNEL_STATE:
return StateUtil.stringOrNull(etherIface.getState());
case MikrotikBindingConstants.CHANNEL_RATE:
return StateUtil.stringOrNull(etherIface.getRate());
default:
return UnDefType.UNDEF;
}
}
protected State getCapIterfaceChannelState(String channelID) {
RouterosCapInterface capIface = (RouterosCapInterface) this.iface;
if (capIface == null) {
return UnDefType.UNDEF;
}
switch (channelID) {
case MikrotikBindingConstants.CHANNEL_STATE:
return StateUtil.stringOrNull(capIface.getCurrentState());
case MikrotikBindingConstants.CHANNEL_RATE:
return StateUtil.stringOrNull(capIface.getRateSet());
case MikrotikBindingConstants.CHANNEL_REGISTERED_CLIENTS:
return StateUtil.intOrNull(capIface.getRegisteredClients());
case MikrotikBindingConstants.CHANNEL_AUTHORIZED_CLIENTS:
return StateUtil.intOrNull(capIface.getAuthorizedClients());
default:
return UnDefType.UNDEF;
}
}
protected State getWlanIterfaceChannelState(String channelID) {
RouterosWlanInterface wlIface = (RouterosWlanInterface) this.iface;
if (wlIface == null) {
return UnDefType.UNDEF;
}
switch (channelID) {
case MikrotikBindingConstants.CHANNEL_STATE:
return StateUtil.stringOrNull(wlIface.getCurrentState());
case MikrotikBindingConstants.CHANNEL_RATE:
return StateUtil.stringOrNull(wlIface.getRate());
case MikrotikBindingConstants.CHANNEL_REGISTERED_CLIENTS:
return StateUtil.intOrNull(wlIface.getRegisteredClients());
case MikrotikBindingConstants.CHANNEL_AUTHORIZED_CLIENTS:
return StateUtil.intOrNull(wlIface.getAuthorizedClients());
default:
return UnDefType.UNDEF;
}
}
protected State getPPPoECliChannelState(String channelID) {
RouterosPPPoECliInterface pppCli = (RouterosPPPoECliInterface) this.iface;
if (pppCli == null) {
return UnDefType.UNDEF;
}
switch (channelID) {
case MikrotikBindingConstants.CHANNEL_STATE:
return StateUtil.stringOrNull(pppCli.getStatus());
case MikrotikBindingConstants.CHANNEL_UP_SINCE:
return StateUtil.timeOrNull(pppCli.getUptimeStart());
default:
return UnDefType.UNDEF;
}
}
protected State getL2TPSrvChannelState(String channelID) {
RouterosL2TPSrvInterface vpnSrv = (RouterosL2TPSrvInterface) this.iface;
if (vpnSrv == null) {
return UnDefType.UNDEF;
}
switch (channelID) {
case MikrotikBindingConstants.CHANNEL_STATE:
return StateUtil.stringOrNull(vpnSrv.getEncoding());
case MikrotikBindingConstants.CHANNEL_UP_SINCE:
return StateUtil.timeOrNull(vpnSrv.getUptimeStart());
default:
return UnDefType.UNDEF;
}
}
protected State getL2TPCliChannelState(String channelID) {
RouterosL2TPCliInterface vpnCli = (RouterosL2TPCliInterface) this.iface;
if (vpnCli == null) {
return UnDefType.UNDEF;
}
switch (channelID) {
case MikrotikBindingConstants.CHANNEL_STATE:
return StateUtil.stringOrNull(vpnCli.getEncoding());
case MikrotikBindingConstants.CHANNEL_UP_SINCE:
return StateUtil.timeOrNull(vpnCli.getUptimeStart());
default:
return UnDefType.UNDEF;
}
}
@Override
protected void executeCommand(ChannelUID channelUID, Command command) {
if (iface == null) {
return;
}
logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID);
}
}

View File

@ -0,0 +1,292 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.handler;
import static org.openhab.core.thing.ThingStatus.ONLINE;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.MikrotikBindingConstants;
import org.openhab.binding.mikrotik.internal.config.RouterosThingConfig;
import org.openhab.binding.mikrotik.internal.model.RouterosDevice;
import org.openhab.binding.mikrotik.internal.model.RouterosRouterboardInfo;
import org.openhab.binding.mikrotik.internal.model.RouterosSystemResources;
import org.openhab.binding.mikrotik.internal.util.StateUtil;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import me.legrange.mikrotik.MikrotikApiException;
/**
* The {@link MikrotikRouterosBridgeHandler} is a main binding class that wraps a {@link RouterosDevice} and
* manages fetching data from RouterOS. It is also responsible for updating brindge thing properties and
* handling commands, which are sent to one of the channels and emit channel updates whenever required.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class MikrotikRouterosBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(MikrotikRouterosBridgeHandler.class);
private @Nullable RouterosThingConfig config;
private @Nullable volatile RouterosDevice routeros;
private @Nullable ScheduledFuture<?> refreshJob;
private Map<String, State> currentState = new HashMap<>();
public static boolean supportsThingType(ThingTypeUID thingTypeUID) {
return MikrotikBindingConstants.THING_TYPE_ROUTEROS.equals(thingTypeUID);
}
public MikrotikRouterosBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
public void initialize() {
cancelRefreshJob();
var cfg = getConfigAs(RouterosThingConfig.class);
this.config = cfg;
logger.debug("Initializing MikrotikRouterosBridgeHandler with config = {}", cfg);
if (cfg.isValid()) {
this.routeros = new RouterosDevice(cfg.host, cfg.port, cfg.login, cfg.password);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, String.format("Connecting to %s", cfg.host));
scheduleRefreshJob();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration is not valid");
}
}
public @Nullable RouterosDevice getRouteros() {
return routeros;
}
public @Nullable RouterosThingConfig getBridgeConfig() {
return config;
}
@Override
protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
if (status == ThingStatus.ONLINE
|| (status == ThingStatus.OFFLINE && statusDetail == ThingStatusDetail.COMMUNICATION_ERROR)) {
scheduleRefreshJob();
} else if (status == ThingStatus.OFFLINE && statusDetail == ThingStatusDetail.CONFIGURATION_ERROR) {
cancelRefreshJob();
}
// update the status only if it's changed
ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(status, statusDetail).withDescription(description)
.build();
if (!statusInfo.equals(getThing().getStatusInfo())) {
super.updateStatus(status, statusDetail, description);
}
}
@Override
public void dispose() {
cancelRefreshJob();
var routeros = this.routeros;
if (routeros != null) {
routeros.stop();
this.routeros = null;
}
}
private void scheduleRefreshJob() {
synchronized (this) {
var cfg = this.config;
if (refreshJob == null) {
int refreshPeriod = 10;
if (cfg != null) {
refreshPeriod = cfg.refresh;
} else {
logger.warn("null config spotted in scheduleRefreshJob");
}
logger.debug("Scheduling refresh job every {}s", refreshPeriod);
refreshJob = scheduler.scheduleWithFixedDelay(this::scheduledRun, 0, refreshPeriod, TimeUnit.SECONDS);
}
}
}
private void cancelRefreshJob() {
synchronized (this) {
var job = this.refreshJob;
if (job != null) {
logger.debug("Cancelling refresh job");
job.cancel(true);
this.refreshJob = null;
}
}
}
private void scheduledRun() {
var routeros = this.routeros;
if (routeros == null) {
logger.error("RouterOS device is null in scheduledRun");
return;
}
if (!routeros.isConnected()) {
// Perform connection
try {
logger.debug("Starting routeros model");
routeros.start();
RouterosRouterboardInfo rbInfo = routeros.getRouterboardInfo();
if (rbInfo != null) {
Map<String, String> bridgeProps = editProperties();
bridgeProps.put(MikrotikBindingConstants.PROPERTY_MODEL, rbInfo.getModel());
bridgeProps.put(MikrotikBindingConstants.PROPERTY_FIRMWARE, rbInfo.getFirmware());
bridgeProps.put(MikrotikBindingConstants.PROPERTY_SERIAL_NUMBER, rbInfo.getSerialNumber());
updateProperties(bridgeProps);
} else {
logger.warn("Failed to set RouterBOARD properties for bridge {}", getThing().getUID());
}
updateStatus(ThingStatus.ONLINE);
} catch (MikrotikApiException e) {
logger.warn("Error while logging in to RouterOS {} | Cause: {}", getThing().getUID(), e, e.getCause());
String errorMessage = e.getMessage();
if (errorMessage == null) {
errorMessage = "Error connecting (UNKNOWN ERROR)";
}
if (errorMessage.contains("Command timed out") || errorMessage.contains("Error connecting")) {
routeros.stop();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
} else if (errorMessage.contains("Connection refused")) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Remote host refused to connect, make sure port is correct");
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage);
}
}
} else {
// We're connected - do a usual polling cycle
performRefresh();
}
}
private void performRefresh() {
var routeros = this.routeros;
if (routeros == null) {
logger.error("RouterOS device is null in performRefresh");
return;
}
try {
logger.debug("Refreshing RouterOS caches for {}", getThing().getUID());
routeros.refresh();
// refresh own channels
for (Channel channel : getThing().getChannels()) {
try {
refreshChannel(channel.getUID());
} catch (RuntimeException e) {
throw new ChannelUpdateException(getThing().getUID(), channel.getUID(), e);
}
}
// refresh all the client things below
getThing().getThings().forEach(thing -> {
ThingHandler handler = thing.getHandler();
if (handler instanceof MikrotikBaseThingHandler<?>) {
((MikrotikBaseThingHandler<?>) handler).refresh();
}
});
} catch (ChannelUpdateException e) {
logger.debug("Error updating channel! {}", e.getMessage(), e.getCause());
} catch (MikrotikApiException e) {
logger.error("RouterOS cache refresh failed in {} due to Mikrotik API error", getThing().getUID(), e);
routeros.stop();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
} catch (Exception e) {
logger.error("Unhandled exception while refreshing the {} RouterOS model", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Handling command = {} for channel = {}", command, channelUID);
if (getThing().getStatus() == ONLINE) {
RouterosDevice routeros = getRouteros();
if (routeros != null) {
if (command == REFRESH) {
refreshChannel(channelUID);
} else {
logger.warn("Ignoring command = {} for channel = {} as it is not yet supported", command,
channelUID);
}
}
}
}
protected void refreshChannel(ChannelUID channelUID) {
RouterosDevice routerOs = getRouteros();
String channelID = channelUID.getIdWithoutGroup();
RouterosSystemResources rbRes = null;
if (routerOs != null) {
rbRes = routerOs.getSysResources();
}
State oldState = currentState.getOrDefault(channelID, UnDefType.NULL);
State newState = oldState;
if (rbRes == null) {
newState = UnDefType.NULL;
} else {
switch (channelID) {
case MikrotikBindingConstants.CHANNEL_UP_SINCE:
newState = StateUtil.timeOrNull(rbRes.getUptimeStart());
break;
case MikrotikBindingConstants.CHANNEL_FREE_SPACE:
newState = StateUtil.qtyBytesOrNull(rbRes.getFreeSpace());
break;
case MikrotikBindingConstants.CHANNEL_TOTAL_SPACE:
newState = StateUtil.qtyBytesOrNull(rbRes.getTotalSpace());
break;
case MikrotikBindingConstants.CHANNEL_USED_SPACE:
newState = StateUtil.qtyPercentOrNull(rbRes.getSpaceUse());
break;
case MikrotikBindingConstants.CHANNEL_FREE_MEM:
newState = StateUtil.qtyBytesOrNull(rbRes.getFreeMem());
break;
case MikrotikBindingConstants.CHANNEL_TOTAL_MEM:
newState = StateUtil.qtyBytesOrNull(rbRes.getTotalMem());
break;
case MikrotikBindingConstants.CHANNEL_USED_MEM:
newState = StateUtil.qtyPercentOrNull(rbRes.getMemUse());
break;
case MikrotikBindingConstants.CHANNEL_CPU_LOAD:
newState = StateUtil.qtyPercentOrNull(rbRes.getCpuLoad());
break;
}
}
if (!newState.equals(oldState)) {
updateState(channelID, newState);
currentState.put(channelID, newState);
}
}
}

View File

@ -0,0 +1,239 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.handler;
import static org.openhab.binding.mikrotik.internal.MikrotikBindingConstants.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.MikrotikBindingConstants;
import org.openhab.binding.mikrotik.internal.config.WirelessClientThingConfig;
import org.openhab.binding.mikrotik.internal.model.RouterosCapsmanRegistration;
import org.openhab.binding.mikrotik.internal.model.RouterosDevice;
import org.openhab.binding.mikrotik.internal.model.RouterosRegistrationBase;
import org.openhab.binding.mikrotik.internal.model.RouterosWirelessRegistration;
import org.openhab.binding.mikrotik.internal.util.RateCalculator;
import org.openhab.binding.mikrotik.internal.util.StateUtil;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MikrotikWirelessClientThingHandler} is a {@link MikrotikBaseThingHandler} subclass that wraps shared
* functionality for all wireless clients listed either in CAPsMAN or Wireless RouterOS sections.
* It is responsible for handling commands, which are sent to one of the channels and emit channel updates whenever
* required.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class MikrotikWirelessClientThingHandler extends MikrotikBaseThingHandler<WirelessClientThingConfig> {
private final Logger logger = LoggerFactory.getLogger(MikrotikWirelessClientThingHandler.class);
private @Nullable RouterosRegistrationBase wifiReg;
private boolean online = false;
private boolean continuousConnection = false;
private @Nullable LocalDateTime lastSeen;
private final RateCalculator txByteRate = new RateCalculator(BigDecimal.ZERO);
private final RateCalculator rxByteRate = new RateCalculator(BigDecimal.ZERO);
private final RateCalculator txPacketRate = new RateCalculator(BigDecimal.ZERO);
private final RateCalculator rxPacketRate = new RateCalculator(BigDecimal.ZERO);
public static boolean supportsThingType(ThingTypeUID thingTypeUID) {
return MikrotikBindingConstants.THING_TYPE_WIRELESS_CLIENT.equals(thingTypeUID);
}
public MikrotikWirelessClientThingHandler(Thing thing) {
super(thing);
}
private boolean fetchModels() {
var cfg = this.config;
if (cfg != null) {
RouterosDevice routeros = getRouterOs();
RouterosWirelessRegistration wifiRegistration = null;
if (routeros != null) {
wifiRegistration = routeros.findWirelessRegistration(cfg.mac);
}
this.wifiReg = wifiRegistration;
if (wifiRegistration != null && !cfg.ssid.isBlank()
&& !cfg.ssid.equalsIgnoreCase(wifiRegistration.getSSID())) {
this.wifiReg = null;
}
if (this.wifiReg == null && routeros != null) {
// try looking in capsman when there is no wirelessRegistration
RouterosCapsmanRegistration capsmanReg = routeros.findCapsmanRegistration(cfg.mac);
this.wifiReg = capsmanReg;
if (capsmanReg != null && !cfg.ssid.isBlank() && !cfg.ssid.equalsIgnoreCase(capsmanReg.getSSID())) {
this.wifiReg = null;
}
}
}
return this.wifiReg != null;
}
@Override
protected void refreshModels() {
this.online = fetchModels();
if (online) {
lastSeen = LocalDateTime.now();
} else {
continuousConnection = false;
}
var wifiReg = this.wifiReg;
if (wifiReg != null) {
var cfg = this.config;
int considerContinuous = 180;
if (cfg != null) {
considerContinuous = cfg.considerContinuous;
}
txByteRate.update(wifiReg.getTxBytes());
rxByteRate.update(wifiReg.getRxBytes());
txPacketRate.update(wifiReg.getTxPackets());
rxPacketRate.update(wifiReg.getRxPackets());
LocalDateTime uptimeStart = wifiReg.getUptimeStart();
continuousConnection = (uptimeStart != null)
&& LocalDateTime.now().isAfter(uptimeStart.plusSeconds(considerContinuous));
}
}
@Override
protected void refreshChannel(ChannelUID channelUID) {
var wifiReg = this.wifiReg;
if (wifiReg == null) {
logger.warn("wifiReg is null in refreshChannel({})", channelUID);
return;
}
String channelID = channelUID.getIdWithoutGroup();
State oldState = currentState.getOrDefault(channelID, UnDefType.NULL);
State newState = oldState;
if (channelID.equals(CHANNEL_CONNECTED)) {
newState = StateUtil.boolOrNull(online);
} else if (channelID.equals(CHANNEL_LAST_SEEN)) {
newState = StateUtil.timeOrNull(lastSeen);
} else if (channelID.equals(CHANNEL_CONTINUOUS)) {
newState = StateUtil.boolOrNull(continuousConnection);
} else if (!online) {
newState = UnDefType.NULL;
} else {
switch (channelID) {
case CHANNEL_NAME:
newState = StateUtil.stringOrNull(wifiReg.getName());
break;
case CHANNEL_COMMENT:
newState = StateUtil.stringOrNull(wifiReg.getComment());
break;
case CHANNEL_MAC:
newState = StateUtil.stringOrNull(wifiReg.getMacAddress());
break;
case CHANNEL_INTERFACE:
newState = StateUtil.stringOrNull(wifiReg.getInterfaceName());
break;
case CHANNEL_SSID:
newState = StateUtil.stringOrNull(wifiReg.getSSID());
break;
case CHANNEL_UP_SINCE:
newState = StateUtil.timeOrNull(wifiReg.getUptimeStart());
break;
case CHANNEL_TX_DATA_RATE:
newState = StateUtil.qtyMegabitPerSecOrNull(txByteRate.getMegabitRate());
break;
case CHANNEL_RX_DATA_RATE:
newState = StateUtil.qtyMegabitPerSecOrNull(rxByteRate.getMegabitRate());
break;
case CHANNEL_TX_PACKET_RATE:
newState = StateUtil.floatOrNull(txPacketRate.getRate());
break;
case CHANNEL_RX_PACKET_RATE:
newState = StateUtil.floatOrNull(rxPacketRate.getRate());
break;
case CHANNEL_TX_BYTES:
newState = StateUtil.bigIntOrNull(wifiReg.getTxBytes());
break;
case CHANNEL_RX_BYTES:
newState = StateUtil.bigIntOrNull(wifiReg.getRxBytes());
break;
case CHANNEL_TX_PACKETS:
newState = StateUtil.bigIntOrNull(wifiReg.getTxPackets());
break;
case CHANNEL_RX_PACKETS:
newState = StateUtil.bigIntOrNull(wifiReg.getRxPackets());
break;
default:
newState = UnDefType.NULL;
if (wifiReg instanceof RouterosWirelessRegistration) {
newState = getWirelessRegistrationChannelState(channelID);
} else if (wifiReg instanceof RouterosCapsmanRegistration) {
newState = getCapsmanRegistrationChannelState(channelID);
}
}
}
if (!newState.equals(oldState)) {
updateState(channelID, newState);
currentState.put(channelID, newState);
}
}
@SuppressWarnings("null")
protected State getCapsmanRegistrationChannelState(String channelID) {
if (this.wifiReg == null) {
return UnDefType.UNDEF;
}
RouterosCapsmanRegistration capsmanReg = (RouterosCapsmanRegistration) this.wifiReg;
switch (channelID) {
case CHANNEL_SIGNAL:
return StateUtil.intOrNull(capsmanReg.getRxSignal());
default:
return UnDefType.UNDEF;
}
}
@SuppressWarnings("null")
protected State getWirelessRegistrationChannelState(String channelID) {
if (this.wifiReg == null) {
return UnDefType.UNDEF;
}
RouterosWirelessRegistration wirelessReg = (RouterosWirelessRegistration) this.wifiReg;
switch (channelID) {
case CHANNEL_SIGNAL:
return StateUtil.intOrNull(wirelessReg.getRxSignal());
default:
return UnDefType.UNDEF;
}
}
@Override
protected void executeCommand(ChannelUID channelUID, Command command) {
if (!online) {
return;
}
logger.warn("Ignoring unsupported command = {} for channel = {}", command, channelUID);
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.math.BigInteger;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link RouterosBaseData} is a base class for other data models having internal hashmap access methods and
* values convertors.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public abstract class RouterosBaseData {
private final Map<String, String> propMap;
public RouterosBaseData(Map<String, String> props) {
this.propMap = props;
}
public void mergeProps(Map<String, String> otherProps) {
this.propMap.putAll(otherProps);
}
protected boolean hasProp(String key) {
return propMap.containsKey(key);
}
protected String getProp(String key, String defaultValue) {
return propMap.getOrDefault(key, defaultValue);
}
protected void setProp(String key, String value) {
propMap.put(key, value);
}
protected @Nullable String getProp(String key) {
return propMap.get(key);
}
protected @Nullable Integer getIntProp(String key) {
String val = propMap.get(key);
return val == null ? null : Integer.valueOf(val);
}
protected @Nullable BigInteger getBigIntProp(String key) {
String val = propMap.get(key);
return val == null ? null : new BigInteger(propMap.getOrDefault(key, "0"));
}
protected @Nullable Float getFloatProp(String key) {
String val = propMap.get(key);
return val == null ? null : Float.valueOf(val);
}
}

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link RouterosBridgeInterface} is a model class for `bridge` interface models having casting accessors for
* data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosBridgeInterface extends RouterosInterfaceBase {
public RouterosBridgeInterface(Map<String, String> props) {
super(props);
}
@Override
public RouterosInterfaceType getDesignedType() {
return RouterosInterfaceType.BRIDGE;
}
@Override
public boolean hasDetailedReport() {
return false;
}
@Override
public boolean hasMonitor() {
return false;
}
}

View File

@ -0,0 +1,78 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link RouterosCapInterface} is a model class for `cap` interface models having casting accessors for
* data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosCapInterface extends RouterosInterfaceBase {
public RouterosCapInterface(Map<String, String> props) {
super(props);
}
@Override
public RouterosInterfaceType getDesignedType() {
return RouterosInterfaceType.CAP;
}
@Override
public boolean hasDetailedReport() {
return true;
}
@Override
public boolean hasMonitor() {
return false;
}
public boolean isMaster() {
return getProp("slave", "").equals("false");
}
public boolean isDynamic() {
return getProp("dynamic", "").equals("true");
}
public boolean isBound() {
return getProp("bound", "").equals("true");
}
public boolean isActive() {
return getProp("inactive", "").equals("false");
}
public @Nullable String getCurrentState() {
return getProp("current-state");
}
public @Nullable String getRateSet() {
return getProp("current-basic-rate-set");
}
public @Nullable Integer getRegisteredClients() {
return getIntProp("current-registered-clients");
}
public @Nullable Integer getAuthorizedClients() {
return getIntProp("current-authorized-clients");
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link RouterosCapsmanRegistration} is a model class for WiFi client data retrieced from CAPsMAN controller
* in RouterOS. Is a subclass of {@link RouterosRegistrationBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosCapsmanRegistration extends RouterosRegistrationBase {
public RouterosCapsmanRegistration(Map<String, String> props) {
super(props);
}
public @Nullable String getIdentity() {
return getProp("eap-identity");
}
public @Nullable Integer getRxSignal() {
return getIntProp("rx-signal");
}
}

View File

@ -0,0 +1,301 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.net.SocketFactory;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import me.legrange.mikrotik.ApiConnection;
import me.legrange.mikrotik.ApiConnectionException;
import me.legrange.mikrotik.MikrotikApiException;
/**
* The {@link RouterosDevice} class is wrapped inside a bridge thing and responsible for communication with
* Mikrotik device, data fetching, caching and aggregation.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosDevice {
private final Logger logger = LoggerFactory.getLogger(RouterosDevice.class);
private final String host;
private final int port;
private final int connectionTimeout;
private final String login;
private final String password;
private @Nullable ApiConnection connection;
public static final String PROP_ID_KEY = ".id";
public static final String PROP_TYPE_KEY = "type";
public static final String PROP_NAME_KEY = "name";
public static final String PROP_SSID_KEY = "ssid";
private static final String CMD_PRINT_IFACES = "/interface/print";
private static final String CMD_PRINT_IFACE_TYPE_TPL = "/interface/%s/print";
private static final String CMD_MONTOR_IFACE_MONITOR_TPL = "/interface/%s/monitor numbers=%s once";
private static final String CMD_PRINT_CAPS_IFACES = "/caps-man/interface/print";
private static final String CMD_PRINT_CAPSMAN_REGS = "/caps-man/registration-table/print";
private static final String CMD_PRINT_WIRELESS_REGS = "/interface/wireless/registration-table/print";
private static final String CMD_PRINT_RESOURCE = "/system/resource/print";
private static final String CMD_PRINT_RB_INFO = "/system/routerboard/print";
private final List<RouterosInterfaceBase> interfaceCache = new ArrayList<>();
private final List<RouterosCapsmanRegistration> capsmanRegistrationCache = new ArrayList<>();
private final List<RouterosWirelessRegistration> wirelessRegistrationCache = new ArrayList<>();
private final Set<String> monitoredInterfaces = new HashSet<>();
private final Map<String, String> wlanSsid = new HashMap<>();
private @Nullable RouterosSystemResources resourcesCache;
private @Nullable RouterosRouterboardInfo rbInfo;
private static Optional<RouterosInterfaceBase> createTypedInterface(Map<String, String> interfaceProps) {
RouterosInterfaceType ifaceType = RouterosInterfaceType.resolve(interfaceProps.get(PROP_TYPE_KEY));
if (ifaceType == null) {
return Optional.empty();
}
switch (ifaceType) {
case ETHERNET:
return Optional.of(new RouterosEthernetInterface(interfaceProps));
case BRIDGE:
return Optional.of(new RouterosBridgeInterface(interfaceProps));
case CAP:
return Optional.of(new RouterosCapInterface(interfaceProps));
case WLAN:
return Optional.of(new RouterosWlanInterface(interfaceProps));
case PPPOE_CLIENT:
return Optional.of(new RouterosPPPoECliInterface(interfaceProps));
case L2TP_SERVER:
return Optional.of(new RouterosL2TPSrvInterface(interfaceProps));
case L2TP_CLIENT:
return Optional.of(new RouterosL2TPCliInterface(interfaceProps));
default:
return Optional.empty();
}
}
public RouterosDevice(String host, int port, String login, String password) {
this.host = host;
this.port = port;
this.login = login;
this.password = password;
this.connectionTimeout = ApiConnection.DEFAULT_CONNECTION_TIMEOUT;
}
public boolean isConnected() {
ApiConnection conn = this.connection;
return conn != null && conn.isConnected();
}
public void start() throws MikrotikApiException {
login();
updateRouterboardInfo();
}
public void stop() {
ApiConnection conn = this.connection;
if (conn != null && conn.isConnected()) {
logout();
}
}
public void login() throws MikrotikApiException {
logger.debug("Attempting login to {} ...", host);
ApiConnection conn = ApiConnection.connect(SocketFactory.getDefault(), host, port, connectionTimeout);
conn.login(login, password);
logger.debug("Logged in to RouterOS at {} !", host);
this.connection = conn;
}
public void logout() {
ApiConnection conn = this.connection;
logger.debug("Logging out of {}", host);
if (conn != null) {
logger.debug("Closing connection to {}", host);
try {
conn.close();
} catch (ApiConnectionException e) {
logger.debug("Logout error", e);
} finally {
this.connection = null;
}
}
}
public boolean registerForMonitoring(String interfaceName) {
return monitoredInterfaces.add(interfaceName);
}
public boolean unregisterForMonitoring(String interfaceName) {
return monitoredInterfaces.remove(interfaceName);
}
public void refresh() throws MikrotikApiException {
synchronized (this) {
updateResources();
updateInterfaceData();
updateCapsmanRegistrations();
updateWirelessRegistrations();
}
}
public @Nullable RouterosRouterboardInfo getRouterboardInfo() {
return rbInfo;
}
public @Nullable RouterosSystemResources getSysResources() {
return resourcesCache;
}
public @Nullable RouterosCapsmanRegistration findCapsmanRegistration(String macAddress) {
Optional<RouterosCapsmanRegistration> searchResult = capsmanRegistrationCache.stream()
.filter(registration -> macAddress.equalsIgnoreCase(registration.getMacAddress())).findFirst();
return searchResult.orElse(null);
}
public @Nullable RouterosWirelessRegistration findWirelessRegistration(String macAddress) {
Optional<RouterosWirelessRegistration> searchResult = wirelessRegistrationCache.stream()
.filter(registration -> macAddress.equalsIgnoreCase(registration.getMacAddress())).findFirst();
return searchResult.orElse(null);
}
@SuppressWarnings("null")
public @Nullable RouterosInterfaceBase findInterface(String name) {
Optional<RouterosInterfaceBase> searchResult = interfaceCache.stream()
.filter(iface -> iface.getName() != null && iface.getName().equalsIgnoreCase(name)).findFirst();
return searchResult.orElse(null);
}
@SuppressWarnings("null")
private void updateInterfaceData() throws MikrotikApiException {
ApiConnection conn = this.connection;
if (conn == null) {
return;
}
List<Map<String, String>> ifaceResponse = conn.execute(CMD_PRINT_IFACES);
Set<String> interfaceTypesToPoll = new HashSet<>();
this.wlanSsid.clear();
this.interfaceCache.clear();
ifaceResponse.forEach(props -> {
Optional<RouterosInterfaceBase> ifaceOpt = createTypedInterface(props);
if (ifaceOpt.isPresent()) {
RouterosInterfaceBase iface = ifaceOpt.get();
if (iface.hasDetailedReport()) {
interfaceTypesToPoll.add(iface.getApiType());
}
this.interfaceCache.add(iface);
}
});
Map<String, Map<String, String>> typedIfaceResponse = new HashMap<>();
for (String ifaceApiType : interfaceTypesToPoll) {
String cmd = String.format(CMD_PRINT_IFACE_TYPE_TPL, ifaceApiType);
if (ifaceApiType.compareTo("cap") == 0) {
cmd = CMD_PRINT_CAPS_IFACES;
}
connection.execute(cmd).forEach(propMap -> {
String ifaceName = propMap.get(PROP_NAME_KEY);
if (ifaceName != null) {
if (typedIfaceResponse.containsKey(ifaceName)) {
typedIfaceResponse.get(ifaceName).putAll(propMap);
} else {
typedIfaceResponse.put(ifaceName, propMap);
}
}
});
}
for (RouterosInterfaceBase ifaceModel : interfaceCache) {
// Enrich with detailed data
Map<String, String> additionalIfaceProps = typedIfaceResponse.get(ifaceModel.getName());
if (additionalIfaceProps != null) {
ifaceModel.mergeProps(additionalIfaceProps);
}
// Get monitor data
if (ifaceModel.hasMonitor() && monitoredInterfaces.contains(ifaceModel.getName())) {
String cmd = String.format(CMD_MONTOR_IFACE_MONITOR_TPL, ifaceModel.getApiType(), ifaceModel.getName());
List<Map<String, String>> monitorProps = connection.execute(cmd);
ifaceModel.mergeProps(monitorProps.get(0));
}
// Note SSIDs for non-CAPsMAN wireless clients
String ifaceName = ifaceModel.getName();
String ifaceSsid = ifaceModel.getProperty(PROP_SSID_KEY);
if (ifaceName != null && ifaceSsid != null && !ifaceName.isBlank() && !ifaceSsid.isBlank()) {
this.wlanSsid.put(ifaceName, ifaceSsid);
}
}
}
private void updateCapsmanRegistrations() throws MikrotikApiException {
ApiConnection conn = this.connection;
if (conn == null) {
return;
}
List<Map<String, String>> response = conn.execute(CMD_PRINT_CAPSMAN_REGS);
if (response != null) {
capsmanRegistrationCache.clear();
response.forEach(reg -> capsmanRegistrationCache.add(new RouterosCapsmanRegistration(reg)));
}
}
private void updateWirelessRegistrations() throws MikrotikApiException {
ApiConnection conn = this.connection;
if (conn == null) {
return;
}
List<Map<String, String>> response = conn.execute(CMD_PRINT_WIRELESS_REGS);
wirelessRegistrationCache.clear();
response.forEach(props -> {
String wlanIfaceName = props.get("interface");
String wlanSsidName = wlanSsid.get(wlanIfaceName);
if (wlanSsidName != null && wlanIfaceName != null && !wlanIfaceName.isBlank() && !wlanSsidName.isBlank()) {
props.put(PROP_SSID_KEY, wlanSsidName);
}
wirelessRegistrationCache.add(new RouterosWirelessRegistration(props));
});
}
private void updateResources() throws MikrotikApiException {
ApiConnection conn = this.connection;
if (conn == null) {
return;
}
List<Map<String, String>> response = conn.execute(CMD_PRINT_RESOURCE);
this.resourcesCache = new RouterosSystemResources(response.get(0));
}
private void updateRouterboardInfo() throws MikrotikApiException {
ApiConnection conn = this.connection;
if (conn == null) {
return;
}
List<Map<String, String>> response = conn.execute(CMD_PRINT_RB_INFO);
this.rbInfo = new RouterosRouterboardInfo(response.get(0));
}
}

View File

@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link RouterosEthernetInterface} is a model class for `ether` interface models having casting accessors for
* data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosEthernetInterface extends RouterosInterfaceBase {
public RouterosEthernetInterface(Map<String, String> props) {
super(props);
}
@Override
public RouterosInterfaceType getDesignedType() {
return RouterosInterfaceType.ETHERNET;
}
@Override
public String getApiType() {
return "ethernet";
}
@Override
public boolean hasDetailedReport() {
return true;
}
@Override
public boolean hasMonitor() {
// PowerLine interfaces are of ehter type too
String name = getDefaultName();
return name != null && !name.startsWith("pwr");
}
public @Nullable String getDefaultName() {
return getProp("default-name");
}
public @Nullable String getRate() {
return getProp("rate");
}
public @Nullable String getState() {
return getProp("status");
}
}

View File

@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import static org.openhab.binding.mikrotik.internal.model.RouterosDevice.PROP_ID_KEY;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.util.Converter;
/**
* The {@link RouterosInterfaceBase} is a base model class for network interface models having casting accessors for
* data that is same for all interface types.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public abstract class RouterosInterfaceBase extends RouterosBaseData {
protected @Nullable RouterosInterfaceType type;
public RouterosInterfaceBase(Map<String, String> props) {
super(props);
this.type = RouterosInterfaceType.resolve(getType());
}
public @Nullable String getProperty(String propName) {
return getProp(propName);
}
public abstract RouterosInterfaceType getDesignedType();
public abstract boolean hasDetailedReport();
public abstract boolean hasMonitor();
public String getApiType() {
return getDesignedType().toString();
};
public boolean validate() {
return getDesignedType() == this.type;
}
public @Nullable String getId() {
return getProp(PROP_ID_KEY);
}
public @Nullable String getType() {
return getProp("type");
}
public @Nullable String getName() {
return getProp("name");
}
public @Nullable String getComment() {
return getProp("comment");
}
public @Nullable String getMacAddress() {
return getProp("mac-address");
}
public boolean isEnabled() {
return getProp("disabled", "").equals("false");
}
public boolean isConnected() {
return getProp("running", "").equals("true");
}
public @Nullable Integer getLinkDowns() {
return getIntProp("link-downs");
}
public @Nullable LocalDateTime getLastLinkDownTime() {
return Converter.fromRouterosTime(getProp("last-link-down-time"));
}
public @Nullable LocalDateTime getLastLinkUpTime() {
return Converter.fromRouterosTime(getProp("last-link-up-time"));
}
public @Nullable BigInteger getTxBytes() {
return getBigIntProp("tx-byte");
}
public @Nullable BigInteger getRxBytes() {
return getBigIntProp("rx-byte");
}
public @Nullable BigInteger getTxPackets() {
return getBigIntProp("tx-packet");
}
public @Nullable BigInteger getRxPackets() {
return getBigIntProp("rx-packet");
}
public @Nullable BigInteger getTxDrops() {
return getBigIntProp("tx-drop");
}
public @Nullable BigInteger getRxDrops() {
return getBigIntProp("rx-drop");
}
public @Nullable BigInteger getTxErrors() {
return getBigIntProp("tx-error");
}
public @Nullable BigInteger getRxErrors() {
return getBigIntProp("rx-error");
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link RouterosInterfaceType} enum wraps RouterOS network interface type strings.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public enum RouterosInterfaceType {
ETHERNET("ether"),
BRIDGE("bridge"),
WLAN("wlan"),
CAP("cap"),
PPPOE_CLIENT("pppoe-out"),
L2TP_SERVER("l2tp-in"),
L2TP_CLIENT("l2tp-out");
private final String typeName;
RouterosInterfaceType(String routerosType) {
this.typeName = routerosType;
}
public boolean equalsName(String otherType) {
// (otherName == null) check is not needed because name.equals(null) returns false
return typeName.equals(otherType);
}
public String toString() {
return this.typeName;
}
public @Nullable static RouterosInterfaceType resolve(@Nullable String routerosType) {
for (RouterosInterfaceType current : RouterosInterfaceType.values()) {
if (current.typeName.equals(routerosType)) {
return current;
}
}
return null;
}
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.util.Converter;
/**
* The {@link RouterosL2TPCliInterface} is a model class for `l2tp-out` interface models having casting accessors for
* data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosL2TPCliInterface extends RouterosInterfaceBase {
public RouterosL2TPCliInterface(Map<String, String> props) {
super(props);
}
@Override
public RouterosInterfaceType getDesignedType() {
return RouterosInterfaceType.L2TP_SERVER;
}
@Override
public String getApiType() {
return "l2tp-client";
}
@Override
public boolean hasDetailedReport() {
return false;
}
@Override
public boolean hasMonitor() {
return true;
}
public @Nullable String getStatus() {
return getProp("status");
}
public @Nullable String getEncoding() {
return String.format("Encoding: %s", getProp("encoding", "None"));
}
public @Nullable String getServerAddress() {
return getProp("connect-to");
}
public @Nullable String getLocalAddress() {
return getProp("local-address");
}
public @Nullable String getRemoteAddress() {
return getProp("remote-address");
}
public @Nullable String getUptime() {
return getProp("uptime");
}
public @Nullable LocalDateTime getUptimeStart() {
return Converter.routerosPeriodBack(getUptime());
}
}

View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.util.Converter;
/**
* The {@link RouterosL2TPSrvInterface} is a model class for `l2tp-in` interface models having casting accessors for
* data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosL2TPSrvInterface extends RouterosInterfaceBase {
public RouterosL2TPSrvInterface(Map<String, String> props) {
super(props);
}
@Override
public RouterosInterfaceType getDesignedType() {
return RouterosInterfaceType.L2TP_SERVER;
}
@Override
public String getApiType() {
return "l2tp-server";
}
@Override
public boolean hasDetailedReport() {
return true;
}
@Override
public boolean hasMonitor() {
return false;
}
public @Nullable String getStatus() {
return getProp("status");
}
public @Nullable String getEncoding() {
return String.format("Encoding: %s", getProp("encoding", "None"));
}
public @Nullable String getClientAddress() {
return getProp("client-address");
}
public @Nullable String getUptime() {
return getProp("uptime");
}
public @Nullable LocalDateTime getUptimeStart() {
return Converter.routerosPeriodBack(getUptime());
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.util.Converter;
/**
* The {@link RouterosPPPoECliInterface} is a model class for `pppoe-out` interface models having casting accessors for
* data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosPPPoECliInterface extends RouterosInterfaceBase {
public RouterosPPPoECliInterface(Map<String, String> props) {
super(props);
}
@Override
public RouterosInterfaceType getDesignedType() {
return RouterosInterfaceType.PPPOE_CLIENT;
}
@Override
public String getApiType() {
return "pppoe-client";
}
@Override
public boolean hasDetailedReport() {
return false;
}
@Override
public boolean hasMonitor() {
return true;
}
public @Nullable String getMacAddress() {
return getProp("ac-mac");
}
public @Nullable String getStatus() {
return getProp("status");
}
public @Nullable String getUptime() {
return getProp("uptime");
}
public @Nullable LocalDateTime getUptimeStart() {
return Converter.routerosPeriodBack(getUptime());
}
}

View File

@ -0,0 +1,105 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import static org.openhab.binding.mikrotik.internal.model.RouterosDevice.PROP_ID_KEY;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.util.Converter;
/**
* The {@link RouterosRegistrationBase} is a base model class for WiFi client models having casting accessors for
* data that is same for all WiFi client types.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosRegistrationBase extends RouterosBaseData {
public RouterosRegistrationBase(Map<String, String> props) {
super(props);
this.postProcess();
}
protected void postProcess() {
if (hasProp("bytes")) {
String bytesStr = getProp("bytes");
if (bytesStr != null) {
String[] bytes = bytesStr.split(",");
setProp("tx-byte", bytes[0]);
setProp("rx-byte", bytes[1]);
}
}
if (hasProp("packets")) {
String packetsStr = getProp("packets");
if (packetsStr != null) {
String[] packets = packetsStr.split(",");
setProp("tx-packet", packets[0]);
setProp("rx-packet", packets[1]);
}
}
}
public @Nullable String getId() {
return getProp(PROP_ID_KEY);
}
public @Nullable String getName() {
return getProp("name");
}
public @Nullable String getComment() {
return getProp("comment");
}
public @Nullable String getMacAddress() {
return getProp("mac-address");
}
public @Nullable String getSSID() {
return getProp("ssid");
}
public @Nullable String getInterfaceName() {
return getProp("interface");
}
public @Nullable BigInteger getTxBytes() {
return getBigIntProp("tx-byte");
}
public @Nullable BigInteger getRxBytes() {
return getBigIntProp("rx-byte");
}
public @Nullable BigInteger getTxPackets() {
return getBigIntProp("tx-packet");
}
public @Nullable BigInteger getRxPackets() {
return getBigIntProp("rx-packet");
}
public @Nullable String getUptime() {
return getProp("uptime");
}
public @Nullable LocalDateTime getUptimeStart() {
return Converter.routerosPeriodBack(getUptime());
}
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link RouterosRouterboardInfo} is a model class for RouterOS system info used as bridge thing property values.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosRouterboardInfo extends RouterosBaseData {
public RouterosRouterboardInfo(Map<String, String> props) {
super(props);
}
public String getFirmware() {
return String.format("v%s (%s)", getFirmwareVersion(), getFirmwareType());
}
public boolean isRouterboard() {
return getProp("routerboard", "").equals("true");
}
public String getModel() {
return getProp("model", "Unknown");
}
public String getSerialNumber() {
return getProp("serial-number", "XXX");
}
public String getFirmwareType() {
return getProp("firmware-type", "N/A");
}
public String getFirmwareVersion() {
return getProp("current-firmware", "Unknown");
}
}

View File

@ -0,0 +1,78 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.util.Converter;
/**
* The {@link RouterosSystemResources} is a model class for RouterOS system info having casting accessors for
* data that is available through bridge thing channels.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosSystemResources extends RouterosBaseData {
public RouterosSystemResources(Map<String, String> props) {
super(props);
}
public @Nullable Integer getFreeSpace() {
return getIntProp("free-hdd-space");
}
public @Nullable Integer getTotalSpace() {
return getIntProp("total-hdd-space");
}
public @Nullable Integer getSpaceUse() {
Integer freeSpace = getFreeSpace(), totalSpace = getTotalSpace();
if (freeSpace == null || totalSpace == null) {
return null;
}
return 100 - Math.round(100F * freeSpace / totalSpace);
}
public @Nullable Integer getFreeMem() {
return getIntProp("free-memory");
}
public @Nullable Integer getTotalMem() {
return getIntProp("total-memory");
}
public @Nullable Integer getMemUse() {
Integer freeMem = getFreeMem(), totalMem = getTotalMem();
if (freeMem == null || totalMem == null) {
return null;
}
return 100 - Math.round(100F * freeMem / totalMem);
}
public @Nullable Integer getCpuLoad() {
return getIntProp("cpu-load");
}
public @Nullable String getUptime() {
return getProp("uptime");
}
public @Nullable LocalDateTime getUptimeStart() {
return Converter.routerosPeriodBack(getUptime());
}
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.time.LocalDateTime;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mikrotik.internal.util.Converter;
/**
* The {@link RouterosWirelessRegistration} is a model class for WiFi client data retrieced from RouterOS
* physical wireless interface. Is a subclass of {@link RouterosRegistrationBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosWirelessRegistration extends RouterosRegistrationBase {
public RouterosWirelessRegistration(Map<String, String> props) {
super(props);
}
public int getRxSignal() {
String signalValue = getProp("signal-strength", "0@hz").split("@")[0];
return Integer.parseInt(signalValue);
}
public @Nullable LocalDateTime getLastActivity() {
return Converter.routerosPeriodBack(getProp("last-activity"));
}
}

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.model;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link RouterosWlanInterface} is a model class for `waln` interface models having casting accessors for
* data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RouterosWlanInterface extends RouterosInterfaceBase {
public RouterosWlanInterface(Map<String, String> props) {
super(props);
}
@Override
public RouterosInterfaceType getDesignedType() {
return RouterosInterfaceType.WLAN;
}
@Override
public String getApiType() {
return "wireless";
}
@Override
public boolean hasDetailedReport() {
return true;
}
@Override
public boolean hasMonitor() {
return true;
}
public boolean isMaster() {
return !getProp("master-interface", "").isBlank();
}
public @Nullable String getCurrentState() {
return getProp("status");
}
public @Nullable String getSSID() {
return getProp("ssid");
}
public @Nullable String getMode() {
return getProp("mode");
}
public @Nullable String getRate() {
return getProp("band");
}
public @Nullable String getInterfaceType() {
return getProp("interface-type");
}
public int getRegisteredClients() {
Integer registeredClients = getIntProp("registered-clients");
return registeredClients == null ? 0 : registeredClients;
}
public int getAuthorizedClients() {
Integer authedClients = getIntProp("authenticated-clients");
return authedClients == null ? 0 : authedClients;
}
}

View File

@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link Converter} is a utility class having functions to convert RouterOS-specific data representation strings
* to regular Java types.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class Converter {
private static final DateTimeFormatter ROUTEROS_FORMAT = DateTimeFormatter.ofPattern("MMM/dd/yyyy kk:mm:ss",
Locale.ENGLISH);
private static final Pattern PERIOD_PATTERN = Pattern.compile("(\\d+)([a-z]+){1,3}");
public @Nullable static LocalDateTime fromRouterosTime(@Nullable String dateTimeString) {
if (dateTimeString == null) {
return null;
}
String fixedTs = dateTimeString.substring(0, 1).toUpperCase() + dateTimeString.substring(1);
return LocalDateTime.parse(fixedTs, ROUTEROS_FORMAT);
}
public @Nullable static LocalDateTime routerosPeriodBack(@Nullable String durationString) {
return routerosPeriodBack(durationString, LocalDateTime.now());
}
public @Nullable static LocalDateTime routerosPeriodBack(@Nullable String durationString,
LocalDateTime fromDateTime) {
if (durationString == null) {
return null;
}
Matcher m = PERIOD_PATTERN.matcher(durationString);
LocalDateTime ts = fromDateTime;
while (m.find()) {
int amount = Integer.parseInt(m.group(1));
String periodKey = m.group(2);
switch (periodKey) {
case "y":
ts = ts.minusYears(amount);
break;
case "w":
ts = ts.minusWeeks(amount);
break;
case "d":
ts = ts.minusDays(amount);
break;
case "h":
ts = ts.minusHours(amount);
break;
case "m":
ts = ts.minusMinutes(amount);
break;
case "s":
ts = ts.minusSeconds(amount);
break;
case "ms":
ts = ts.plus(amount, ChronoField.MILLI_OF_SECOND.getBaseUnit());
break;
default:
throw new IllegalArgumentException(
String.format("Unable to parse duration %s - %s is unknown", durationString, periodKey));
}
}
return ts;
}
}

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.util;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.threeten.extra.Seconds;
/**
* The {@link RateCalculator} is used to calculate data changing rate as number per second. Has a separate method
* to get megabits per second rate out of byte number.
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class RateCalculator {
public static final int BYTES_IN_MEGABIT = 125000;
private BigDecimal value;
float rate;
LocalDateTime lastUpdated;
public RateCalculator(BigDecimal initialValue) {
this.value = initialValue;
this.lastUpdated = LocalDateTime.now();
this.rate = 0.0F;
}
public float getRate() {
return this.rate;
}
public float getMegabitRate() {
return getRate() / BYTES_IN_MEGABIT;
}
public void update(@Nullable BigDecimal currentValue) {
if (currentValue != null) {
synchronized (this) {
LocalDateTime thisUpdated = LocalDateTime.now();
Seconds secDiff = Seconds.between(lastUpdated, thisUpdated);
this.rate = currentValue.subtract(value).floatValue() / secDiff.getAmount();
this.value = currentValue;
this.lastUpdated = thisUpdated;
}
}
}
public void update(@Nullable BigInteger currentValue) {
BigInteger val = currentValue == null ? BigInteger.ZERO : currentValue;
this.update(new BigDecimal(val));
}
}

View File

@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.util;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.ZoneId;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
/**
* The {@link StateUtil} class holds static methods to cast Java native/class types to OpenHAB values
*
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class StateUtil {
public static State stringOrNull(@Nullable String value) {
return value == null ? UnDefType.NULL : new StringType(value);
}
public static State qtyMegabitPerSecOrNull(@Nullable Float value) {
return value == null ? UnDefType.NULL : new QuantityType<>(value, Units.MEGABIT_PER_SECOND);
}
public static State qtyPercentOrNull(@Nullable Integer value) {
return value == null ? UnDefType.NULL : new QuantityType<>(value, Units.PERCENT);
}
public static State qtyBytesOrNull(@Nullable Integer value) {
return value == null ? UnDefType.NULL : new QuantityType<>(value, Units.BYTE);
}
public static State intOrNull(@Nullable Integer value) {
return value == null ? UnDefType.NULL : new DecimalType(value.floatValue());
}
public static State bigIntOrNull(@Nullable BigInteger value) {
return value == null ? UnDefType.NULL : DecimalType.valueOf(value.toString());
}
public static State floatOrNull(@Nullable Float value) {
return value == null ? UnDefType.NULL : new DecimalType(value);
}
public static State boolOrNull(@Nullable Boolean value) {
if (value == null) {
return UnDefType.NULL;
}
return value ? OnOffType.ON : OnOffType.OFF;
}
public static State timeOrNull(@Nullable LocalDateTime value) {
return value == null ? UnDefType.NULL : new DateTimeType(value.atZone(ZoneId.systemDefault()));
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="mikrotik" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Mikrotik Binding</name>
<description>
This is the binding for integrating Mikrotik RouterOS powered devices (routers, access points, switches,
etc) to facilitate WiFi clients and network interface tracking.
</description>
</binding:binding>

View File

@ -0,0 +1,414 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="mikrotik"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="routeros">
<label>Mikrotik RouterOS</label>
<description>A connection to RouterOS device</description>
<channels>
<channel id="freeSpace" typeId="freeSpace"/>
<channel id="totalSpace" typeId="totalSpace"/>
<channel id="usedSpace" typeId="usedSpace"/>
<channel id="freeMemory" typeId="freeMemory"/>
<channel id="totalMemory" typeId="totalMemory"/>
<channel id="usedMemory" typeId="usedMemory"/>
<channel id="cpuLoad" typeId="cpuLoad"/>
<channel id="upSince" typeId="upSince"/>
</channels>
<properties>
<property name="vendor">Mikrotik</property>
<property name="modelId">RouterOS</property>
<property name="firmware"/>
<property name="serial"/>
</properties>
<representation-property>name</representation-property>
<config-description>
<parameter name="host" type="text" required="true">
<label>Hostname</label>
<description>Hostname or IP address of the RouterOS device</description>
<default>192.168.88.1</default>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" max="65535" min="1" required="false">
<label>API Port</label>
<description>API Port number of the RouterOS device</description>
<default>8728</default>
</parameter>
<parameter name="login" type="text" required="true">
<label>Username</label>
<default>admin</default>
<description>The username to access the the RouterOS device</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>The user password to access the RouterOS device</description>
<context>password</context>
</parameter>
<parameter name="refresh" type="integer" required="false">
<label>Refresh Interval</label>
<description>The refresh interval in seconds to poll the RouterOS device</description>
<default>10</default>
</parameter>
</config-description>
</bridge-type>
<thing-type id="interface">
<supported-bridge-type-refs>
<bridge-type-ref id="routeros"/>
</supported-bridge-type-refs>
<label>RouterOS Interface</label>
<description>A network interface from RouterOS system (ethernet, wifi, vpn, etc.)</description>
<channels>
<!-- common channels for all interface types -->
<channel id="type" typeId="interfaceType"/>
<channel id="name" typeId="interfaceName"/>
<channel id="comment" typeId="comment"/>
<channel id="macAddress" typeId="macAddress"/>
<channel id="enabled" typeId="enabled"/>
<channel id="connected" typeId="connected"/>
<channel id="lastLinkDownTime" typeId="lastLinkDownTime"/>
<channel id="lastLinkUpTime" typeId="lastLinkUpTime"/>
<channel id="linkDowns" typeId="linkDowns"/>
<channel id="txRate" typeId="txRate"/>
<channel id="rxRate" typeId="rxRate"/>
<channel id="txPacketRate" typeId="txPacketRate"/>
<channel id="rxPacketRate" typeId="rxPacketRate"/>
<channel id="txBytes" typeId="txBytes"/>
<channel id="rxBytes" typeId="rxBytes"/>
<channel id="txPackets" typeId="txPackets"/>
<channel id="rxPackets" typeId="rxPackets"/>
<channel id="txDrops" typeId="txDrops"/>
<channel id="rxDrops" typeId="rxDrops"/>
<channel id="txErrors" typeId="txErrors"/>
<channel id="rxErrors" typeId="rxErrors"/>
<!-- ethernet interface channels -->
<channel id="defaultName" typeId="defaultName"/>
<channel id="rate" typeId="ethernetRate"/>
<!-- cap interface channels -->
<channel id="state" typeId="state"/>
<channel id="registeredClients" typeId="registeredClients"/>
<channel id="authorizedClients" typeId="authorizedClients"/>
<!-- ppp & vpn interface channels -->
<channel id="upSince" typeId="upSince"/>
</channels>
<representation-property>name</representation-property>
<config-description>
<parameter name="name" type="text" required="true">
<label>Interface Name</label>
<description>RouterOS Interface name (i.e. ether1)</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="wifiRegistration">
<supported-bridge-type-refs>
<bridge-type-ref id="routeros"/>
</supported-bridge-type-refs>
<label>RouterOS Wireless Client</label>
<description>A wireless client connected to a RouterOS wireless network (direct or CAPsMAN-managed)</description>
<channels>
<channel id="macAddress" typeId="macAddress"/>
<channel id="comment" typeId="comment"/>
<channel id="connected" typeId="connected"/>
<channel id="continuous" typeId="continuous"/>
<channel id="ssid" typeId="ssid"/>
<channel id="interface" typeId="interfaceName"/>
<channel id="signal" typeId="system.signal-strength"/>
<channel id="upSince" typeId="upSince"/>
<channel id="lastSeen" typeId="lastSeen"/>
<channel id="txRate" typeId="txRate"/>
<channel id="rxRate" typeId="rxRate"/>
<channel id="txPacketRate" typeId="txPacketRate"/>
<channel id="rxPacketRate" typeId="rxPacketRate"/>
<channel id="txBytes" typeId="txBytes"/>
<channel id="rxBytes" typeId="rxBytes"/>
<channel id="txPackets" typeId="txPackets"/>
<channel id="rxPackets" typeId="rxPackets"/>
</channels>
<representation-property>macAddress</representation-property>
<config-description>
<parameter name="mac" type="text" required="true">
<label>Client MAC</label>
<description>WiFi client MAC address</description>
</parameter>
<parameter name="ssid" type="text" required="false">
<label>SSID</label>
<description>
Constraining SSID for the WiFi client (optional). If client will connect to another SSID,
this thing
will stay offline until client reconnects to specified SSID.
</description>
</parameter>
<parameter name="considerContinuous" type="integer" required="false">
<label>Consider Home Interval</label>
<description>The interval in seconds to treat the client as connected permanently</description>
<default>180</default>
</parameter>
</config-description>
</thing-type>
<channel-type id="freeSpace" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Free Space</label>
<description>Amount of free storage left on device in bytes</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="totalSpace" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Total Space</label>
<description>Amount of total storage available on device in bytes</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="usedSpace">
<item-type>Number:Dimensionless</item-type>
<label>Used Space %</label>
<description>Percentage of used device storage space</description>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="freeMemory" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Free RAM</label>
<description>Amount of free memory left on device in bytes</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="totalMemory" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Total RAM</label>
<description>Amount of total memory available on device in bytes</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="usedMemory">
<item-type>Number:Dimensionless</item-type>
<label>Used RAM %</label>
<description>Percentage of used device memory</description>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="cpuLoad">
<item-type>Number:Dimensionless</item-type>
<label>CPU Load %</label>
<description>CPU load percentage</description>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="interfaceType" advanced="true">
<item-type>String</item-type>
<label>Interface Type</label>
<description>Network interface type</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="interfaceName" advanced="true">
<item-type>String</item-type>
<label>Interface Name</label>
<description>Network interface name</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="comment" advanced="true">
<item-type>String</item-type>
<label>Comment</label>
<description>User-defined comment</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="macAddress" advanced="true">
<item-type>String</item-type>
<label>MAC Address</label>
<description>MAC address of the client or interface</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="enabled">
<item-type>Switch</item-type>
<label>Enabled</label>
<description>Reflects enabled or disabled state</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="connected">
<item-type>Switch</item-type>
<label>Connected</label>
<description>Reflects connected or disconnected state</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="continuous">
<item-type>Switch</item-type>
<label>Continuous</label>
<description>Connection is considered long-running</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="lastLinkDownTime">
<item-type>DateTime</item-type>
<label>Last Link Down</label>
<description>Last time when link went down</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="lastLinkUpTime">
<item-type>DateTime</item-type>
<label>Last Link Up</label>
<description>Last time when link went up</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="linkDowns" advanced="true">
<item-type>Number</item-type>
<label>Link Downs</label>
<description>Amount of link downs</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="txRate">
<item-type>Number:DataTransferRate</item-type>
<label>Transmission Rate</label>
<description>Rate of data transmission in megabits per second</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rxRate">
<item-type>Number:DataTransferRate</item-type>
<label>Receiving Rate</label>
<description>Rate of data receiving in megabits per second</description>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="txPacketRate" advanced="true">
<item-type>Number</item-type>
<label>Transmission Packet Rate</label>
<description>Rate of data transmission in packets per second</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="rxPacketRate" advanced="true">
<item-type>Number</item-type>
<label>Receiving Packet Rate</label>
<description>Rate of data receiving in packets per second</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="txBytes" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Transmitted Bytes</label>
<description>Amount of bytes transmitted</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="rxBytes" advanced="true">
<item-type>Number:DataAmount</item-type>
<label>Received Bytes</label>
<description>Amount of bytes received</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="txPackets" advanced="true">
<item-type>Number</item-type>
<label>Transmitted Packets</label>
<description>Amount of packets transmitted</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="rxPackets" advanced="true">
<item-type>Number</item-type>
<label>Received Packets</label>
<description>Amount of packets received</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="txDrops" advanced="true">
<item-type>Number</item-type>
<label>Transmission Drops</label>
<description>Amount of packets dropped during transmission</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="rxDrops" advanced="true">
<item-type>Number</item-type>
<label>Receiving Drops</label>
<description>Amount of packets dropped during receiving</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="txErrors" advanced="true">
<item-type>Number</item-type>
<label>Transmission Errors</label>
<description>Amount of errors during transmission</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="rxErrors" advanced="true">
<item-type>Number</item-type>
<label>Receiving Errors</label>
<description>Amount of errors during receiving</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="defaultName" advanced="true">
<item-type>String</item-type>
<label>Default Name</label>
<description>Interface factory name</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="ethernetRate" advanced="true">
<item-type>String</item-type>
<label>Link Rate</label>
<description>Ethernet link rate</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="state">
<item-type>String</item-type>
<label>State</label>
<description>WiFi interface state</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="registeredClients">
<item-type>Number</item-type>
<label>Registered Clients</label>
<description>Amount of clients registered to WiFi interface</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="authorizedClients">
<item-type>Number</item-type>
<label>Authorized Clients</label>
<description>Amount of clients authorized by WiFi interface</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="upSince">
<item-type>DateTime</item-type>
<label>Up since</label>
<description>Time when thing got up</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="lastSeen">
<item-type>DateTime</item-type>
<label>Last Seen</label>
<description>Time of when the client was last seen connected</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="ssid">
<item-type>String</item-type>
<label>WiFi Network</label>
<description>Wireless Network (SSID) the wireless client is connected to</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.mikrotik.internal.util;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertNull;
import java.time.LocalDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* @author Oleg Vivtash - Initial contribution
*/
@NonNullByDefault
public class ConverterTest {
@Test
public void testFromRouterosTime() {
assertThat(Converter.fromRouterosTime("dec/11/2020 20:45:40"),
is(equalTo(LocalDateTime.of(2020, 12, 11, 20, 45, 40, 0))));
assertThat(Converter.fromRouterosTime("jan/07/2021 09:14:11"),
is(equalTo(LocalDateTime.of(2021, 1, 7, 9, 14, 11, 0))));
assertThat(Converter.fromRouterosTime("feb/13/2021 23:59:59"),
is(equalTo(LocalDateTime.of(2021, 2, 13, 23, 59, 59, 0))));
}
@Test
public void testFromRouterosPeriod() {
LocalDateTime fromDateTime = LocalDateTime.of(2021, 2, 1, 0, 0, 0, 0);
assertThat(Converter.routerosPeriodBack("1y3w4d5h6m7s11ms", fromDateTime),
is(equalTo(LocalDateTime.parse("2020-01-06T18:53:53.011"))));
assertNull(Converter.routerosPeriodBack(null));
/*
* uptime = 6w6h31m31s
* uptime = 3d7h6m43s710ms
* uptime = 16h39m58s220ms
* uptime = 1h38m53s110ms
* uptime = 53m53s950ms
*/
assertThat(Converter.routerosPeriodBack("6w6h31m31s", fromDateTime),
is(equalTo(LocalDateTime.parse("2020-12-20T17:28:29"))));
assertThat(Converter.routerosPeriodBack("3d7h6m43s710ms", fromDateTime),
is(equalTo(LocalDateTime.parse("2021-01-28T16:53:17.710"))));
assertThat(Converter.routerosPeriodBack("16h39m58s220ms", fromDateTime),
is(equalTo(LocalDateTime.parse("2021-01-31T07:20:02.220"))));
assertThat(Converter.routerosPeriodBack("1h38m53s110ms", fromDateTime),
is(equalTo(LocalDateTime.parse("2021-01-31T22:21:07.110"))));
assertThat(Converter.routerosPeriodBack("53m53s950ms", fromDateTime),
is(equalTo(LocalDateTime.parse("2021-01-31T23:06:07.950"))));
}
}

View File

@ -206,6 +206,7 @@
<module>org.openhab.binding.mielecloud</module> <module>org.openhab.binding.mielecloud</module>
<module>org.openhab.binding.mihome</module> <module>org.openhab.binding.mihome</module>
<module>org.openhab.binding.miio</module> <module>org.openhab.binding.miio</module>
<module>org.openhab.binding.mikrotik</module>
<module>org.openhab.binding.millheat</module> <module>org.openhab.binding.millheat</module>
<module>org.openhab.binding.milight</module> <module>org.openhab.binding.milight</module>
<module>org.openhab.binding.minecraft</module> <module>org.openhab.binding.minecraft</module>