diff --git a/CODEOWNERS b/CODEOWNERS index 5471b3eb9ad..a9bb24764bb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -172,6 +172,7 @@ /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 diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 3c984e667d6..c86eafdcd7a 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -848,6 +848,11 @@ org.openhab.binding.miio ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.mikrotik + ${project.version} + org.openhab.addons.bundles org.openhab.binding.milight diff --git a/bundles/org.openhab.binding.mikrotik/NOTICE b/bundles/org.openhab.binding.mikrotik/NOTICE new file mode 100644 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.mikrotik/README.md b/bundles/org.openhab.binding.mikrotik/README.md new file mode 100644 index 00000000000..43d74e60634 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/README.md @@ -0,0 +1,374 @@ +# Mikrotik RouterOS Binding + +This binding integrates [Mikrotik](https://mikrotik.com/) [RouterOS](https://help.mikrotik.com/docs/display/ROS/RouterOS) +[devices](https://mikrotik.com/products) allowing monitoring of system resources, network interfaces and WiFi clients. + +## Supported Things + +* `routeros` - An instance of the RouterOS device connection +* `interface` - A network interface inside RouterOS device +* `wifiRegistration` - Any wireless client connected to a RouterOS wireless network (regular or CAPsMAN-managed) + + +## Discovery + +Discovery is currently not supported, but may be implemented in future versions. + + +## Bridge Configuration + +To use this binding you need at least one RouterOS-powered device (Bridge) accessible to the host running +openHAB via network. +Make sure your RouterOS has the API enabled by visiting [IP -> Services](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 System -> Users 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 + } +} +``` diff --git a/bundles/org.openhab.binding.mikrotik/pom.xml b/bundles/org.openhab.binding.mikrotik/pom.xml new file mode 100644 index 00000000000..08a7da4226b --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.2.0-SNAPSHOT + + + org.openhab.binding.mikrotik + + openHAB Add-ons :: Bundles :: Mikrotik Binding + + + + me.legrange + mikrotik + 3.0.7 + compile + + + + diff --git a/bundles/org.openhab.binding.mikrotik/src/main/feature/feature.xml b/bundles/org.openhab.binding.mikrotik/src/main/feature/feature.xml new file mode 100644 index 00000000000..50de4336830 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.mikrotik/${project.version} + + diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/MikrotikBindingConstants.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/MikrotikBindingConstants.java new file mode 100644 index 00000000000..4463c40c7c3 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/MikrotikBindingConstants.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link MikrotikBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class MikrotikBindingConstants { + + private static final String BINDING_ID = "mikrotik"; + + public static final String PROPERTY_MODEL = "modelId"; + public static final String PROPERTY_FIRMWARE = "firmware"; + public static final String PROPERTY_SERIAL_NUMBER = "serial"; + + // List of all Thing Types + public static final ThingTypeUID THING_TYPE_ROUTEROS = new ThingTypeUID(BINDING_ID, "routeros"); + public static final ThingTypeUID THING_TYPE_INTERFACE = new ThingTypeUID(BINDING_ID, "interface"); + public static final ThingTypeUID THING_TYPE_WIRELESS_CLIENT = new ThingTypeUID(BINDING_ID, "wifiRegistration"); + + // RouterOS system stats + public static final String CHANNEL_FREE_SPACE = "freeSpace"; + public static final String CHANNEL_TOTAL_SPACE = "totalSpace"; + public static final String CHANNEL_USED_SPACE = "usedSpace"; + public static final String CHANNEL_FREE_MEM = "freeMemory"; + public static final String CHANNEL_TOTAL_MEM = "totalMemory"; + public static final String CHANNEL_USED_MEM = "usedMemory"; + public static final String CHANNEL_CPU_LOAD = "cpuLoad"; + + public static final String CHANNEL_COMMENT = "comment"; + + // List of common interface channels + public static final String CHANNEL_NAME = "name"; + public static final String CHANNEL_TYPE = "type"; + public static final String CHANNEL_MAC = "macAddress"; + public static final String CHANNEL_ENABLED = "enabled"; + public static final String CHANNEL_CONNECTED = "connected"; // used for wifi client as well + public static final String CHANNEL_LAST_LINK_DOWN_TIME = "lastLinkDownTime"; + public static final String CHANNEL_LAST_LINK_UP_TIME = "lastLinkUpTime"; + public static final String CHANNEL_LINK_DOWNS = "linkDowns"; + public static final String CHANNEL_TX_DATA_RATE = "txRate"; + public static final String CHANNEL_RX_DATA_RATE = "rxRate"; + public static final String CHANNEL_TX_PACKET_RATE = "txPacketRate"; + public static final String CHANNEL_RX_PACKET_RATE = "rxPacketRate"; + public static final String CHANNEL_TX_BYTES = "txBytes"; + public static final String CHANNEL_RX_BYTES = "rxBytes"; + public static final String CHANNEL_TX_PACKETS = "txPackets"; + public static final String CHANNEL_RX_PACKETS = "rxPackets"; + public static final String CHANNEL_TX_DROPS = "txDrops"; + public static final String CHANNEL_RX_DROPS = "rxDrops"; + public static final String CHANNEL_TX_ERRORS = "txErrors"; + public static final String CHANNEL_RX_ERRORS = "rxErrors"; + + // Ethernet interface channel list + public static final String CHANNEL_DEFAULT_NAME = "defaultName"; + public static final String CHANNEL_RATE = "rate"; + + // CAPsMAN interface channel list + public static final String CHANNEL_INTERFACE = "interface"; + public static final String CHANNEL_STATE = "state"; + public static final String CHANNEL_REGISTERED_CLIENTS = "registeredClients"; + public static final String CHANNEL_AUTHORIZED_CLIENTS = "authorizedClients"; + public static final String CHANNEL_CONTINUOUS = "continuous"; + + // PPP interface shared channel list + public static final String CHANNEL_UP_SINCE = "upSince"; + + // Wireless client channels + public static final String CHANNEL_LAST_SEEN = "lastSeen"; + public static final String CHANNEL_SSID = "ssid"; + public static final String CHANNEL_SIGNAL = "signal"; + + // List of common wired + wireless client channels + public static final String CHANNEL_SITE = "site"; + public static final String CHANNEL_IP_ADDRESS = "ipAddress"; + public static final String CHANNEL_BLOCKED = "blocked"; + public static final String CHANNEL_RECONNECT = "reconnect"; +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/MikrotikHandlerFactory.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/MikrotikHandlerFactory.java new file mode 100644 index 00000000000..2a3f9cc4c33 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/MikrotikHandlerFactory.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.handler.MikrotikInterfaceThingHandler; +import org.openhab.binding.mikrotik.internal.handler.MikrotikRouterosBridgeHandler; +import org.openhab.binding.mikrotik.internal.handler.MikrotikWirelessClientThingHandler; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Component; + +/** + * The {@link MikrotikHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.mikrotik", service = ThingHandlerFactory.class) +public class MikrotikHandlerFactory extends BaseThingHandlerFactory { + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return MikrotikRouterosBridgeHandler.supportsThingType(thingTypeUID) + || MikrotikWirelessClientThingHandler.supportsThingType(thingTypeUID) + || MikrotikInterfaceThingHandler.supportsThingType(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + if (MikrotikRouterosBridgeHandler.supportsThingType(thingTypeUID)) { + return new MikrotikRouterosBridgeHandler((Bridge) thing); + } else if (MikrotikWirelessClientThingHandler.supportsThingType(thingTypeUID)) { + return new MikrotikWirelessClientThingHandler(thing); + } else if (MikrotikInterfaceThingHandler.supportsThingType(thingTypeUID)) { + return new MikrotikInterfaceThingHandler(thing); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/ConfigValidation.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/ConfigValidation.java new file mode 100644 index 00000000000..e4133bdcad2 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/ConfigValidation.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ConfigValidation} interface should be implemented by all config objects, so the thing handlers could + * change their state properly, based on the config validation result. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public interface ConfigValidation { + boolean isValid(); +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/InterfaceThingConfig.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/InterfaceThingConfig.java new file mode 100644 index 00000000000..5a20255cfa9 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/InterfaceThingConfig.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link InterfaceThingConfig} class contains fields mapping thing configuration parameters for + * network interface things. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class InterfaceThingConfig implements ConfigValidation { + public String name = ""; + + public boolean isValid() { + return !name.isBlank(); + } + + @Override + public String toString() { + return String.format("InterfaceThingConfig{name=%s}", name); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/RouterosThingConfig.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/RouterosThingConfig.java new file mode 100644 index 00000000000..54cf09404c4 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/RouterosThingConfig.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link RouterosThingConfig} class contains fields mapping thing configuration parameters for + * RouterOS bridge thing. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosThingConfig implements ConfigValidation { + public String host = "rb3011"; + public int port = 8728; + public String login = "admin"; + public String password = ""; + public int refresh = 10; + + public boolean isValid() { + return !host.isBlank() && !login.isBlank() && !password.isBlank() && refresh > 0 && port > 0; + } + + @Override + public String toString() { + return String.format("RouterosThingConfig{host=%s, port=%d, login=%s, password=*****, refresh=%ds}", host, port, + login, refresh); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/WirelessClientThingConfig.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/WirelessClientThingConfig.java new file mode 100644 index 00000000000..14cac0effcf --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/config/WirelessClientThingConfig.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WirelessClientThingConfig} class contains fields mapping thing configuration parameters for + * WiFi client thing. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class WirelessClientThingConfig implements ConfigValidation { + public String mac = ""; + public String ssid = ""; + public int considerContinuous = 180; + + public boolean isValid() { + return !mac.isBlank() && considerContinuous > 0; + } + + @Override + public String toString() { + return String.format("WirelessClientThingConfig{mac=%s, ssid=%s, considerContinuous=%ds}", mac, ssid, + considerContinuous); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/ChannelUpdateException.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/ChannelUpdateException.java new file mode 100644 index 00000000000..8e53619a789 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/ChannelUpdateException.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingUID; + +/** + * The {@link ChannelUpdateException} is used to bubble up channel update errors which are mainly + * happens during data conversion. But those errors should not bring bridge offline and break normal + * operation. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class ChannelUpdateException extends RuntimeException { + static final long serialVersionUID = 1L; + + private final ThingUID thingUID; + private final ChannelUID channelID; + + public ChannelUpdateException(ThingUID thingUID, ChannelUID channelUID, Throwable cause) { + super(cause); + this.thingUID = thingUID; + this.channelID = channelUID; + } + + @Override + public @Nullable String getMessage() { + return String.format("%s @ %s/%s", super.getMessage(), thingUID, channelID); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikBaseThingHandler.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikBaseThingHandler.java new file mode 100644 index 00000000000..87bdbae60a5 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikBaseThingHandler.java @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.handler; + +import static org.openhab.core.thing.ThingStatus.OFFLINE; +import static org.openhab.core.thing.ThingStatus.ONLINE; +import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR; +import static org.openhab.core.types.RefreshType.REFRESH; + +import java.lang.reflect.ParameterizedType; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.config.ConfigValidation; +import org.openhab.binding.mikrotik.internal.config.RouterosThingConfig; +import org.openhab.binding.mikrotik.internal.model.RouterosDevice; +import org.openhab.core.cache.ExpiringCache; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MikrotikBaseThingHandler} is a base class for all other RouterOS things of map-value nature. + * It is responsible for handling commands, which are sent to one of the channels and emit channel updates + * whenever required. + * + * @author Oleg Vivtash - Initial contribution + * + * + * @param config - the config class used by this base thing handler + * + */ +@NonNullByDefault +public abstract class MikrotikBaseThingHandler extends BaseThingHandler { + private final Logger logger = LoggerFactory.getLogger(MikrotikBaseThingHandler.class); + protected @Nullable C config; + private @Nullable ScheduledFuture refreshJob; + protected ExpiringCache refreshCache = new ExpiringCache<>(Duration.ofDays(1), () -> false); + protected Map 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); +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikInterfaceThingHandler.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikInterfaceThingHandler.java new file mode 100644 index 00000000000..a7f251f502a --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikInterfaceThingHandler.java @@ -0,0 +1,316 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.handler; + +import static org.openhab.core.thing.ThingStatus.OFFLINE; +import static org.openhab.core.thing.ThingStatus.ONLINE; +import static org.openhab.core.thing.ThingStatusDetail.GONE; + +import java.math.BigDecimal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.MikrotikBindingConstants; +import org.openhab.binding.mikrotik.internal.config.InterfaceThingConfig; +import org.openhab.binding.mikrotik.internal.model.RouterosCapInterface; +import org.openhab.binding.mikrotik.internal.model.RouterosDevice; +import org.openhab.binding.mikrotik.internal.model.RouterosEthernetInterface; +import org.openhab.binding.mikrotik.internal.model.RouterosInterfaceBase; +import org.openhab.binding.mikrotik.internal.model.RouterosL2TPCliInterface; +import org.openhab.binding.mikrotik.internal.model.RouterosL2TPSrvInterface; +import org.openhab.binding.mikrotik.internal.model.RouterosPPPoECliInterface; +import org.openhab.binding.mikrotik.internal.model.RouterosWlanInterface; +import org.openhab.binding.mikrotik.internal.util.RateCalculator; +import org.openhab.binding.mikrotik.internal.util.StateUtil; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MikrotikInterfaceThingHandler} is a {@link MikrotikBaseThingHandler} subclass that wraps shared + * functionality for all interface things of different types. It is responsible for handling commands, which are + * sent to one of the channels and emit channel updates whenever required. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class MikrotikInterfaceThingHandler extends MikrotikBaseThingHandler { + 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); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikRouterosBridgeHandler.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikRouterosBridgeHandler.java new file mode 100644 index 00000000000..e31675ec985 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikRouterosBridgeHandler.java @@ -0,0 +1,292 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.handler; + +import static org.openhab.core.thing.ThingStatus.ONLINE; +import static org.openhab.core.types.RefreshType.REFRESH; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.MikrotikBindingConstants; +import org.openhab.binding.mikrotik.internal.config.RouterosThingConfig; +import org.openhab.binding.mikrotik.internal.model.RouterosDevice; +import org.openhab.binding.mikrotik.internal.model.RouterosRouterboardInfo; +import org.openhab.binding.mikrotik.internal.model.RouterosSystemResources; +import org.openhab.binding.mikrotik.internal.util.StateUtil; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import me.legrange.mikrotik.MikrotikApiException; + +/** + * The {@link MikrotikRouterosBridgeHandler} is a main binding class that wraps a {@link RouterosDevice} and + * manages fetching data from RouterOS. It is also responsible for updating brindge thing properties and + * handling commands, which are sent to one of the channels and emit channel updates whenever required. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class MikrotikRouterosBridgeHandler extends BaseBridgeHandler { + private final Logger logger = LoggerFactory.getLogger(MikrotikRouterosBridgeHandler.class); + private @Nullable RouterosThingConfig config; + private @Nullable volatile RouterosDevice routeros; + private @Nullable ScheduledFuture refreshJob; + private Map 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 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); + } + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikWirelessClientThingHandler.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikWirelessClientThingHandler.java new file mode 100644 index 00000000000..20aec9d57f3 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/handler/MikrotikWirelessClientThingHandler.java @@ -0,0 +1,239 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.handler; + +import static org.openhab.binding.mikrotik.internal.MikrotikBindingConstants.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.MikrotikBindingConstants; +import org.openhab.binding.mikrotik.internal.config.WirelessClientThingConfig; +import org.openhab.binding.mikrotik.internal.model.RouterosCapsmanRegistration; +import org.openhab.binding.mikrotik.internal.model.RouterosDevice; +import org.openhab.binding.mikrotik.internal.model.RouterosRegistrationBase; +import org.openhab.binding.mikrotik.internal.model.RouterosWirelessRegistration; +import org.openhab.binding.mikrotik.internal.util.RateCalculator; +import org.openhab.binding.mikrotik.internal.util.StateUtil; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link MikrotikWirelessClientThingHandler} is a {@link MikrotikBaseThingHandler} subclass that wraps shared + * functionality for all wireless clients listed either in CAPsMAN or Wireless RouterOS sections. + * It is responsible for handling commands, which are sent to one of the channels and emit channel updates whenever + * required. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class MikrotikWirelessClientThingHandler extends MikrotikBaseThingHandler { + 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); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosBaseData.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosBaseData.java new file mode 100644 index 00000000000..3dd9a8416da --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosBaseData.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.math.BigInteger; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link RouterosBaseData} is a base class for other data models having internal hashmap access methods and + * values convertors. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public abstract class RouterosBaseData { + private final Map propMap; + + public RouterosBaseData(Map props) { + this.propMap = props; + } + + public void mergeProps(Map 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); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosBridgeInterface.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosBridgeInterface.java new file mode 100644 index 00000000000..49ba8001457 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosBridgeInterface.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link RouterosBridgeInterface} is a model class for `bridge` interface models having casting accessors for + * data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosBridgeInterface extends RouterosInterfaceBase { + public RouterosBridgeInterface(Map props) { + super(props); + } + + @Override + public RouterosInterfaceType getDesignedType() { + return RouterosInterfaceType.BRIDGE; + } + + @Override + public boolean hasDetailedReport() { + return false; + } + + @Override + public boolean hasMonitor() { + return false; + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosCapInterface.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosCapInterface.java new file mode 100644 index 00000000000..0be4e892ffd --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosCapInterface.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link RouterosCapInterface} is a model class for `cap` interface models having casting accessors for + * data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosCapInterface extends RouterosInterfaceBase { + public RouterosCapInterface(Map 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"); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosCapsmanRegistration.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosCapsmanRegistration.java new file mode 100644 index 00000000000..4d3c200cdf7 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosCapsmanRegistration.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link RouterosCapsmanRegistration} is a model class for WiFi client data retrieced from CAPsMAN controller + * in RouterOS. Is a subclass of {@link RouterosRegistrationBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosCapsmanRegistration extends RouterosRegistrationBase { + public RouterosCapsmanRegistration(Map props) { + super(props); + } + + public @Nullable String getIdentity() { + return getProp("eap-identity"); + } + + public @Nullable Integer getRxSignal() { + return getIntProp("rx-signal"); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosDevice.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosDevice.java new file mode 100644 index 00000000000..47361328167 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosDevice.java @@ -0,0 +1,301 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.net.SocketFactory; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import me.legrange.mikrotik.ApiConnection; +import me.legrange.mikrotik.ApiConnectionException; +import me.legrange.mikrotik.MikrotikApiException; + +/** + * The {@link RouterosDevice} class is wrapped inside a bridge thing and responsible for communication with + * Mikrotik device, data fetching, caching and aggregation. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosDevice { + private final Logger logger = LoggerFactory.getLogger(RouterosDevice.class); + + private final String host; + private final int port; + private final int connectionTimeout; + private final String login; + private final String password; + private @Nullable ApiConnection connection; + + public static final String PROP_ID_KEY = ".id"; + public static final String PROP_TYPE_KEY = "type"; + public static final String PROP_NAME_KEY = "name"; + public static final String PROP_SSID_KEY = "ssid"; + + private static final String CMD_PRINT_IFACES = "/interface/print"; + private static final String CMD_PRINT_IFACE_TYPE_TPL = "/interface/%s/print"; + private static final String CMD_MONTOR_IFACE_MONITOR_TPL = "/interface/%s/monitor numbers=%s once"; + private static final String CMD_PRINT_CAPS_IFACES = "/caps-man/interface/print"; + private static final String CMD_PRINT_CAPSMAN_REGS = "/caps-man/registration-table/print"; + private static final String CMD_PRINT_WIRELESS_REGS = "/interface/wireless/registration-table/print"; + private static final String CMD_PRINT_RESOURCE = "/system/resource/print"; + private static final String CMD_PRINT_RB_INFO = "/system/routerboard/print"; + + private final List interfaceCache = new ArrayList<>(); + private final List capsmanRegistrationCache = new ArrayList<>(); + private final List wirelessRegistrationCache = new ArrayList<>(); + private final Set monitoredInterfaces = new HashSet<>(); + private final Map wlanSsid = new HashMap<>(); + + private @Nullable RouterosSystemResources resourcesCache; + private @Nullable RouterosRouterboardInfo rbInfo; + + private static Optional createTypedInterface(Map 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 searchResult = capsmanRegistrationCache.stream() + .filter(registration -> macAddress.equalsIgnoreCase(registration.getMacAddress())).findFirst(); + return searchResult.orElse(null); + } + + public @Nullable RouterosWirelessRegistration findWirelessRegistration(String macAddress) { + Optional searchResult = wirelessRegistrationCache.stream() + .filter(registration -> macAddress.equalsIgnoreCase(registration.getMacAddress())).findFirst(); + return searchResult.orElse(null); + } + + @SuppressWarnings("null") + public @Nullable RouterosInterfaceBase findInterface(String name) { + Optional 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> ifaceResponse = conn.execute(CMD_PRINT_IFACES); + + Set interfaceTypesToPoll = new HashSet<>(); + this.wlanSsid.clear(); + this.interfaceCache.clear(); + ifaceResponse.forEach(props -> { + Optional ifaceOpt = createTypedInterface(props); + if (ifaceOpt.isPresent()) { + RouterosInterfaceBase iface = ifaceOpt.get(); + if (iface.hasDetailedReport()) { + interfaceTypesToPoll.add(iface.getApiType()); + } + this.interfaceCache.add(iface); + } + }); + + Map> 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 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> 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> 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> 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> 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> response = conn.execute(CMD_PRINT_RB_INFO); + this.rbInfo = new RouterosRouterboardInfo(response.get(0)); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosEthernetInterface.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosEthernetInterface.java new file mode 100644 index 00000000000..ec12087c6e3 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosEthernetInterface.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link RouterosEthernetInterface} is a model class for `ether` interface models having casting accessors for + * data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosEthernetInterface extends RouterosInterfaceBase { + public RouterosEthernetInterface(Map 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"); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosInterfaceBase.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosInterfaceBase.java new file mode 100644 index 00000000000..f9ce78093e0 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosInterfaceBase.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import static org.openhab.binding.mikrotik.internal.model.RouterosDevice.PROP_ID_KEY; + +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.util.Converter; + +/** + * The {@link RouterosInterfaceBase} is a base model class for network interface models having casting accessors for + * data that is same for all interface types. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public abstract class RouterosInterfaceBase extends RouterosBaseData { + protected @Nullable RouterosInterfaceType type; + + public RouterosInterfaceBase(Map 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"); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosInterfaceType.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosInterfaceType.java new file mode 100644 index 00000000000..26d89becbdb --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosInterfaceType.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link RouterosInterfaceType} enum wraps RouterOS network interface type strings. + * + * @author Oleg Vivtash - Initial contribution + */ + +@NonNullByDefault +public enum RouterosInterfaceType { + ETHERNET("ether"), + BRIDGE("bridge"), + WLAN("wlan"), + CAP("cap"), + PPPOE_CLIENT("pppoe-out"), + L2TP_SERVER("l2tp-in"), + L2TP_CLIENT("l2tp-out"); + + private final String typeName; + + RouterosInterfaceType(String routerosType) { + this.typeName = routerosType; + } + + public boolean equalsName(String otherType) { + // (otherName == null) check is not needed because name.equals(null) returns false + return typeName.equals(otherType); + } + + public String toString() { + return this.typeName; + } + + public @Nullable static RouterosInterfaceType resolve(@Nullable String routerosType) { + for (RouterosInterfaceType current : RouterosInterfaceType.values()) { + if (current.typeName.equals(routerosType)) { + return current; + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosL2TPCliInterface.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosL2TPCliInterface.java new file mode 100644 index 00000000000..d4e2c4e3851 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosL2TPCliInterface.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.util.Converter; + +/** + * The {@link RouterosL2TPCliInterface} is a model class for `l2tp-out` interface models having casting accessors for + * data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosL2TPCliInterface extends RouterosInterfaceBase { + public RouterosL2TPCliInterface(Map 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()); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosL2TPSrvInterface.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosL2TPSrvInterface.java new file mode 100644 index 00000000000..37e9beb5ac5 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosL2TPSrvInterface.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.util.Converter; + +/** + * The {@link RouterosL2TPSrvInterface} is a model class for `l2tp-in` interface models having casting accessors for + * data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosL2TPSrvInterface extends RouterosInterfaceBase { + public RouterosL2TPSrvInterface(Map 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()); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosPPPoECliInterface.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosPPPoECliInterface.java new file mode 100644 index 00000000000..34ad4eb549a --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosPPPoECliInterface.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.util.Converter; + +/** + * The {@link RouterosPPPoECliInterface} is a model class for `pppoe-out` interface models having casting accessors for + * data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosPPPoECliInterface extends RouterosInterfaceBase { + public RouterosPPPoECliInterface(Map 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()); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosRegistrationBase.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosRegistrationBase.java new file mode 100644 index 00000000000..a1e978a82c5 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosRegistrationBase.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import static org.openhab.binding.mikrotik.internal.model.RouterosDevice.PROP_ID_KEY; + +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.util.Converter; + +/** + * The {@link RouterosRegistrationBase} is a base model class for WiFi client models having casting accessors for + * data that is same for all WiFi client types. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosRegistrationBase extends RouterosBaseData { + + public RouterosRegistrationBase(Map 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()); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosRouterboardInfo.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosRouterboardInfo.java new file mode 100644 index 00000000000..e8bd0610d10 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosRouterboardInfo.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link RouterosRouterboardInfo} is a model class for RouterOS system info used as bridge thing property values. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosRouterboardInfo extends RouterosBaseData { + + public RouterosRouterboardInfo(Map 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"); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosSystemResources.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosSystemResources.java new file mode 100644 index 00000000000..d230b91c355 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosSystemResources.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.util.Converter; + +/** + * The {@link RouterosSystemResources} is a model class for RouterOS system info having casting accessors for + * data that is available through bridge thing channels. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosSystemResources extends RouterosBaseData { + + public RouterosSystemResources(Map 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()); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosWirelessRegistration.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosWirelessRegistration.java new file mode 100644 index 00000000000..dfc57866561 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosWirelessRegistration.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mikrotik.internal.util.Converter; + +/** + * The {@link RouterosWirelessRegistration} is a model class for WiFi client data retrieced from RouterOS + * physical wireless interface. Is a subclass of {@link RouterosRegistrationBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosWirelessRegistration extends RouterosRegistrationBase { + public RouterosWirelessRegistration(Map 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")); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosWlanInterface.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosWlanInterface.java new file mode 100644 index 00000000000..39ebc28ea6d --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/model/RouterosWlanInterface.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.model; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link RouterosWlanInterface} is a model class for `waln` interface models having casting accessors for + * data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RouterosWlanInterface extends RouterosInterfaceBase { + public RouterosWlanInterface(Map 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; + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/Converter.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/Converter.java new file mode 100644 index 00000000000..0233120cd8e --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/Converter.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.util; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoField; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link Converter} is a utility class having functions to convert RouterOS-specific data representation strings + * to regular Java types. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class Converter { + private static final DateTimeFormatter ROUTEROS_FORMAT = DateTimeFormatter.ofPattern("MMM/dd/yyyy kk:mm:ss", + Locale.ENGLISH); + + private static final Pattern PERIOD_PATTERN = Pattern.compile("(\\d+)([a-z]+){1,3}"); + + public @Nullable static LocalDateTime fromRouterosTime(@Nullable String dateTimeString) { + if (dateTimeString == null) { + return null; + } + String fixedTs = dateTimeString.substring(0, 1).toUpperCase() + dateTimeString.substring(1); + return LocalDateTime.parse(fixedTs, ROUTEROS_FORMAT); + } + + public @Nullable static LocalDateTime routerosPeriodBack(@Nullable String durationString) { + return routerosPeriodBack(durationString, LocalDateTime.now()); + } + + public @Nullable static LocalDateTime routerosPeriodBack(@Nullable String durationString, + LocalDateTime fromDateTime) { + if (durationString == null) { + return null; + } + + Matcher m = PERIOD_PATTERN.matcher(durationString); + LocalDateTime ts = fromDateTime; + while (m.find()) { + int amount = Integer.parseInt(m.group(1)); + String periodKey = m.group(2); + switch (periodKey) { + case "y": + ts = ts.minusYears(amount); + break; + case "w": + ts = ts.minusWeeks(amount); + break; + case "d": + ts = ts.minusDays(amount); + break; + case "h": + ts = ts.minusHours(amount); + break; + case "m": + ts = ts.minusMinutes(amount); + break; + case "s": + ts = ts.minusSeconds(amount); + break; + case "ms": + ts = ts.plus(amount, ChronoField.MILLI_OF_SECOND.getBaseUnit()); + break; + default: + throw new IllegalArgumentException( + String.format("Unable to parse duration %s - %s is unknown", durationString, periodKey)); + } + } + return ts; + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/RateCalculator.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/RateCalculator.java new file mode 100644 index 00000000000..43d86920951 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/RateCalculator.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.util; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.threeten.extra.Seconds; + +/** + * The {@link RateCalculator} is used to calculate data changing rate as number per second. Has a separate method + * to get megabits per second rate out of byte number. + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class RateCalculator { + public static final int BYTES_IN_MEGABIT = 125000; + + private BigDecimal value; + float rate; + LocalDateTime lastUpdated; + + public RateCalculator(BigDecimal initialValue) { + this.value = initialValue; + this.lastUpdated = LocalDateTime.now(); + this.rate = 0.0F; + } + + public float getRate() { + return this.rate; + } + + public float getMegabitRate() { + return getRate() / BYTES_IN_MEGABIT; + } + + public void update(@Nullable BigDecimal currentValue) { + if (currentValue != null) { + synchronized (this) { + LocalDateTime thisUpdated = LocalDateTime.now(); + Seconds secDiff = Seconds.between(lastUpdated, thisUpdated); + this.rate = currentValue.subtract(value).floatValue() / secDiff.getAmount(); + this.value = currentValue; + this.lastUpdated = thisUpdated; + } + } + } + + public void update(@Nullable BigInteger currentValue) { + BigInteger val = currentValue == null ? BigInteger.ZERO : currentValue; + this.update(new BigDecimal(val)); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/StateUtil.java b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/StateUtil.java new file mode 100644 index 00000000000..5f63d1d9508 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/java/org/openhab/binding/mikrotik/internal/util/StateUtil.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.util; + +import java.math.BigInteger; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; + +/** + * The {@link StateUtil} class holds static methods to cast Java native/class types to OpenHAB values + * + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class StateUtil { + + public static State stringOrNull(@Nullable String value) { + return value == null ? UnDefType.NULL : new StringType(value); + } + + public static State qtyMegabitPerSecOrNull(@Nullable Float value) { + return value == null ? UnDefType.NULL : new QuantityType<>(value, Units.MEGABIT_PER_SECOND); + } + + public static State qtyPercentOrNull(@Nullable Integer value) { + return value == null ? UnDefType.NULL : new QuantityType<>(value, Units.PERCENT); + } + + public static State qtyBytesOrNull(@Nullable Integer value) { + return value == null ? UnDefType.NULL : new QuantityType<>(value, Units.BYTE); + } + + public static State intOrNull(@Nullable Integer value) { + return value == null ? UnDefType.NULL : new DecimalType(value.floatValue()); + } + + public static State bigIntOrNull(@Nullable BigInteger value) { + return value == null ? UnDefType.NULL : DecimalType.valueOf(value.toString()); + } + + public static State floatOrNull(@Nullable Float value) { + return value == null ? UnDefType.NULL : new DecimalType(value); + } + + public static State boolOrNull(@Nullable Boolean value) { + if (value == null) { + return UnDefType.NULL; + } + return value ? OnOffType.ON : OnOffType.OFF; + } + + public static State timeOrNull(@Nullable LocalDateTime value) { + return value == null ? UnDefType.NULL : new DateTimeType(value.atZone(ZoneId.systemDefault())); + } +} diff --git a/bundles/org.openhab.binding.mikrotik/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.mikrotik/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 00000000000..86332f0c304 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,12 @@ + + + + Mikrotik Binding + + This is the binding for integrating Mikrotik RouterOS powered devices (routers, access points, switches, + etc) to facilitate WiFi clients and network interface tracking. + + + diff --git a/bundles/org.openhab.binding.mikrotik/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.mikrotik/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 00000000000..7297b93ca53 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,414 @@ + + + + + + A connection to RouterOS device + + + + + + + + + + + + Mikrotik + RouterOS + + + + name + + + + Hostname or IP address of the RouterOS device + 192.168.88.1 + network-address + + + + API Port number of the RouterOS device + 8728 + + + + admin + The username to access the the RouterOS device + + + + The user password to access the RouterOS device + password + + + + The refresh interval in seconds to poll the RouterOS device + 10 + + + + + + + + + + A network interface from RouterOS system (ethernet, wifi, vpn, etc.) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + name + + + + RouterOS Interface name (i.e. ether1) + + + + + + + + + + A wireless client connected to a RouterOS wireless network (direct or CAPsMAN-managed) + + + + + + + + + + + + + + + + + + + + macAddress + + + + WiFi client MAC address + + + + + 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. + + + + + The interval in seconds to treat the client as connected permanently + 180 + + + + + + + Number:DataAmount + + Amount of free storage left on device in bytes + + + + + Number:DataAmount + + Amount of total storage available on device in bytes + + + + + Number:Dimensionless + + Percentage of used device storage space + + + + + Number:DataAmount + + Amount of free memory left on device in bytes + + + + + Number:DataAmount + + Amount of total memory available on device in bytes + + + + + Number:Dimensionless + + Percentage of used device memory + + + + + Number:Dimensionless + + CPU load percentage + + + + + + + String + + Network interface type + + + + + String + + Network interface name + + + + + String + + User-defined comment + + + + + String + + MAC address of the client or interface + + + + + Switch + + Reflects enabled or disabled state + + + + + Switch + + Reflects connected or disconnected state + + + + + Switch + + Connection is considered long-running + + + + + DateTime + + Last time when link went down + + + + + DateTime + + Last time when link went up + + + + + Number + + Amount of link downs + + + + + Number:DataTransferRate + + Rate of data transmission in megabits per second + + + + + Number:DataTransferRate + + Rate of data receiving in megabits per second + + + + + Number + + Rate of data transmission in packets per second + + + + + Number + + Rate of data receiving in packets per second + + + + + Number:DataAmount + + Amount of bytes transmitted + + + + + Number:DataAmount + + Amount of bytes received + + + + + Number + + Amount of packets transmitted + + + + + Number + + Amount of packets received + + + + + Number + + Amount of packets dropped during transmission + + + + + Number + + Amount of packets dropped during receiving + + + + + Number + + Amount of errors during transmission + + + + + Number + + Amount of errors during receiving + + + + + String + + Interface factory name + + + + + String + + Ethernet link rate + + + + + String + + WiFi interface state + + + + + Number + + Amount of clients registered to WiFi interface + + + + + Number + + Amount of clients authorized by WiFi interface + + + + + DateTime + + Time when thing got up + + + + + DateTime + + Time of when the client was last seen connected + + + + + String + + Wireless Network (SSID) the wireless client is connected to + + + + diff --git a/bundles/org.openhab.binding.mikrotik/src/test/java/org/openhab/binding/mikrotik/internal/util/ConverterTest.java b/bundles/org.openhab.binding.mikrotik/src/test/java/org/openhab/binding/mikrotik/internal/util/ConverterTest.java new file mode 100644 index 00000000000..4eb64b17e89 --- /dev/null +++ b/bundles/org.openhab.binding.mikrotik/src/test/java/org/openhab/binding/mikrotik/internal/util/ConverterTest.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2021 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.mikrotik.internal.util; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.time.LocalDateTime; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +/** + * @author Oleg Vivtash - Initial contribution + */ +@NonNullByDefault +public class ConverterTest { + + @Test + public void testFromRouterosTime() { + assertThat(Converter.fromRouterosTime("dec/11/2020 20:45:40"), + is(equalTo(LocalDateTime.of(2020, 12, 11, 20, 45, 40, 0)))); + assertThat(Converter.fromRouterosTime("jan/07/2021 09:14:11"), + is(equalTo(LocalDateTime.of(2021, 1, 7, 9, 14, 11, 0)))); + assertThat(Converter.fromRouterosTime("feb/13/2021 23:59:59"), + is(equalTo(LocalDateTime.of(2021, 2, 13, 23, 59, 59, 0)))); + } + + @Test + public void testFromRouterosPeriod() { + LocalDateTime fromDateTime = LocalDateTime.of(2021, 2, 1, 0, 0, 0, 0); + + assertThat(Converter.routerosPeriodBack("1y3w4d5h6m7s11ms", fromDateTime), + is(equalTo(LocalDateTime.parse("2020-01-06T18:53:53.011")))); + + assertNull(Converter.routerosPeriodBack(null)); + + /* + * uptime = 6w6h31m31s + * uptime = 3d7h6m43s710ms + * uptime = 16h39m58s220ms + * uptime = 1h38m53s110ms + * uptime = 53m53s950ms + */ + + assertThat(Converter.routerosPeriodBack("6w6h31m31s", fromDateTime), + is(equalTo(LocalDateTime.parse("2020-12-20T17:28:29")))); + + assertThat(Converter.routerosPeriodBack("3d7h6m43s710ms", fromDateTime), + is(equalTo(LocalDateTime.parse("2021-01-28T16:53:17.710")))); + + assertThat(Converter.routerosPeriodBack("16h39m58s220ms", fromDateTime), + is(equalTo(LocalDateTime.parse("2021-01-31T07:20:02.220")))); + + assertThat(Converter.routerosPeriodBack("1h38m53s110ms", fromDateTime), + is(equalTo(LocalDateTime.parse("2021-01-31T22:21:07.110")))); + + assertThat(Converter.routerosPeriodBack("53m53s950ms", fromDateTime), + is(equalTo(LocalDateTime.parse("2021-01-31T23:06:07.950")))); + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index 8df22715fdc..73f6869e16a 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -206,6 +206,7 @@ org.openhab.binding.mielecloud org.openhab.binding.mihome org.openhab.binding.miio + org.openhab.binding.mikrotik org.openhab.binding.millheat org.openhab.binding.milight org.openhab.binding.minecraft