/bundles/org.openhab.binding.mielecloud/ @BjoernLange
/bundles/org.openhab.binding.mihome/ @pboos
/bundles/org.openhab.binding.miio/ @marcelrv
+/bundles/org.openhab.binding.mikrotik/ @duhast
/bundles/org.openhab.binding.milight/ @davidgraeff
/bundles/org.openhab.binding.millheat/ @seime
/bundles/org.openhab.binding.minecraft/ @ibaton
<artifactId>org.openhab.binding.miio</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.binding.mikrotik</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.milight</artifactId>
--- /dev/null
+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
--- /dev/null
+# 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
+ }
+}
+```
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+/**
+ * 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";
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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();
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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");
+ }
+}
--- /dev/null
+/**
+ * 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");
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+/**
+ * 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");
+ }
+}
--- /dev/null
+/**
+ * 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");
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
--- /dev/null
+/**
+ * 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");
+ }
+}
--- /dev/null
+/**
+ * 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());
+ }
+}
--- /dev/null
+/**
+ * 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"));
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+/**
+ * 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()));
+ }
+}
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+/**
+ * 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"))));
+ }
+}
<module>org.openhab.binding.mielecloud</module>
<module>org.openhab.binding.mihome</module>
<module>org.openhab.binding.miio</module>
+ <module>org.openhab.binding.mikrotik</module>
<module>org.openhab.binding.millheat</module>
<module>org.openhab.binding.milight</module>
<module>org.openhab.binding.minecraft</module>