2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mikrotik.internal.model;
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.List;
20 import java.util.Optional;
23 import javax.net.SocketFactory;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
30 import me.legrange.mikrotik.ApiConnection;
31 import me.legrange.mikrotik.ApiConnectionException;
32 import me.legrange.mikrotik.MikrotikApiException;
35 * The {@link RouterosDevice} class is wrapped inside a bridge thing and responsible for communication with
36 * Mikrotik device, data fetching, caching and aggregation.
38 * @author Oleg Vivtash - Initial contribution
41 public class RouterosDevice {
42 private final Logger logger = LoggerFactory.getLogger(RouterosDevice.class);
44 private final String host;
45 private final int port;
46 private final int connectionTimeout;
47 private final String login;
48 private final String password;
49 private @Nullable ApiConnection connection;
51 public static final String PROP_ID_KEY = ".id";
52 public static final String PROP_TYPE_KEY = "type";
53 public static final String PROP_NAME_KEY = "name";
54 public static final String PROP_SSID_KEY = "ssid";
56 private static final String CMD_PRINT_IFACES = "/interface/print";
57 private static final String CMD_PRINT_IFACE_TYPE_TPL = "/interface/%s/print";
58 private static final String CMD_MONTOR_IFACE_MONITOR_TPL = "/interface/%s/monitor numbers=%s once";
59 private static final String CMD_PRINT_CAPS_IFACES = "/caps-man/interface/print";
60 private static final String CMD_PRINT_CAPSMAN_REGS = "/caps-man/registration-table/print";
61 private static final String CMD_PRINT_WIRELESS_REGS = "/interface/wireless/registration-table/print";
62 private static final String CMD_PRINT_RESOURCE = "/system/resource/print";
63 private static final String CMD_PRINT_RB_INFO = "/system/routerboard/print";
65 private final List<RouterosInterfaceBase> interfaceCache = new ArrayList<>();
66 private final List<RouterosCapsmanRegistration> capsmanRegistrationCache = new ArrayList<>();
67 private final List<RouterosWirelessRegistration> wirelessRegistrationCache = new ArrayList<>();
68 private final Set<String> monitoredInterfaces = new HashSet<>();
69 private final Map<String, String> wlanSsid = new HashMap<>();
71 private @Nullable RouterosSystemResources resourcesCache;
72 private @Nullable RouterosRouterboardInfo rbInfo;
74 private static Optional<RouterosInterfaceBase> createTypedInterface(Map<String, String> interfaceProps) {
75 RouterosInterfaceType ifaceType = RouterosInterfaceType.resolve(interfaceProps.get(PROP_TYPE_KEY));
76 if (ifaceType == null) {
77 return Optional.empty();
81 return Optional.of(new RouterosEthernetInterface(interfaceProps));
83 return Optional.of(new RouterosBridgeInterface(interfaceProps));
85 return Optional.of(new RouterosCapInterface(interfaceProps));
87 return Optional.of(new RouterosWlanInterface(interfaceProps));
89 return Optional.of(new RouterosPPPoECliInterface(interfaceProps));
91 return Optional.of(new RouterosPPPCliInterface(interfaceProps));
93 return Optional.of(new RouterosL2TPSrvInterface(interfaceProps));
95 return Optional.of(new RouterosL2TPCliInterface(interfaceProps));
97 return Optional.of(new RouterosLTEInterface(interfaceProps));
99 return Optional.empty();
103 public RouterosDevice(String host, int port, String login, String password) {
107 this.password = password;
108 this.connectionTimeout = ApiConnection.DEFAULT_CONNECTION_TIMEOUT;
111 public boolean isConnected() {
112 ApiConnection conn = this.connection;
113 return conn != null && conn.isConnected();
116 public void start() throws MikrotikApiException {
118 updateRouterboardInfo();
122 ApiConnection conn = this.connection;
123 if (conn != null && conn.isConnected()) {
128 public void login() throws MikrotikApiException {
129 logger.debug("Attempting login to {} ...", host);
130 ApiConnection conn = ApiConnection.connect(SocketFactory.getDefault(), host, port, connectionTimeout);
131 conn.login(login, password);
132 logger.debug("Logged in to RouterOS at {} !", host);
133 this.connection = conn;
136 public void logout() {
137 ApiConnection conn = this.connection;
138 logger.debug("Logging out of {}", host);
140 logger.debug("Closing connection to {}", host);
143 } catch (ApiConnectionException e) {
144 logger.debug("Logout error", e);
146 this.connection = null;
151 public boolean registerForMonitoring(String interfaceName) {
152 return monitoredInterfaces.add(interfaceName);
155 public boolean unregisterForMonitoring(String interfaceName) {
156 return monitoredInterfaces.remove(interfaceName);
159 public void refresh() throws MikrotikApiException {
160 synchronized (this) {
162 updateInterfaceData();
163 updateCapsmanRegistrations();
164 updateWirelessRegistrations();
168 public @Nullable RouterosRouterboardInfo getRouterboardInfo() {
172 public @Nullable RouterosSystemResources getSysResources() {
173 return resourcesCache;
176 public @Nullable RouterosCapsmanRegistration findCapsmanRegistration(String macAddress) {
177 Optional<RouterosCapsmanRegistration> searchResult = capsmanRegistrationCache.stream()
178 .filter(registration -> macAddress.equalsIgnoreCase(registration.getMacAddress())).findFirst();
179 return searchResult.orElse(null);
182 public @Nullable RouterosWirelessRegistration findWirelessRegistration(String macAddress) {
183 Optional<RouterosWirelessRegistration> searchResult = wirelessRegistrationCache.stream()
184 .filter(registration -> macAddress.equalsIgnoreCase(registration.getMacAddress())).findFirst();
185 return searchResult.orElse(null);
188 @SuppressWarnings("null")
189 public @Nullable RouterosInterfaceBase findInterface(String name) {
190 Optional<RouterosInterfaceBase> searchResult = interfaceCache.stream()
191 .filter(iface -> iface.getName() != null && iface.getName().equalsIgnoreCase(name)).findFirst();
192 return searchResult.orElse(null);
195 @SuppressWarnings("null")
196 private void updateInterfaceData() throws MikrotikApiException {
197 ApiConnection conn = this.connection;
202 List<Map<String, String>> ifaceResponse = conn.execute(CMD_PRINT_IFACES);
204 Set<String> interfaceTypesToPoll = new HashSet<>();
205 this.wlanSsid.clear();
206 this.interfaceCache.clear();
207 ifaceResponse.forEach(props -> {
208 Optional<RouterosInterfaceBase> ifaceOpt = createTypedInterface(props);
209 if (ifaceOpt.isPresent()) {
210 RouterosInterfaceBase iface = ifaceOpt.get();
211 if (iface.hasDetailedReport()) {
212 interfaceTypesToPoll.add(iface.getApiType());
214 this.interfaceCache.add(iface);
218 Map<String, Map<String, String>> typedIfaceResponse = new HashMap<>();
219 for (String ifaceApiType : interfaceTypesToPoll) {
220 String cmd = String.format(CMD_PRINT_IFACE_TYPE_TPL, ifaceApiType);
221 if (ifaceApiType.compareTo("cap") == 0) {
222 cmd = CMD_PRINT_CAPS_IFACES;
224 connection.execute(cmd).forEach(propMap -> {
225 String ifaceName = propMap.get(PROP_NAME_KEY);
226 if (ifaceName != null) {
227 if (typedIfaceResponse.containsKey(ifaceName)) {
228 typedIfaceResponse.get(ifaceName).putAll(propMap);
230 typedIfaceResponse.put(ifaceName, propMap);
236 for (RouterosInterfaceBase ifaceModel : interfaceCache) {
237 // Enrich with detailed data
239 Map<String, String> additionalIfaceProps = typedIfaceResponse.get(ifaceModel.getName());
240 if (additionalIfaceProps != null) {
241 ifaceModel.mergeProps(additionalIfaceProps);
244 if (ifaceModel.hasMonitor() && monitoredInterfaces.contains(ifaceModel.getName())) {
245 String cmd = String.format(CMD_MONTOR_IFACE_MONITOR_TPL, ifaceModel.getApiType(), ifaceModel.getName());
246 List<Map<String, String>> monitorProps = connection.execute(cmd);
247 ifaceModel.mergeProps(monitorProps.get(0));
249 // Note SSIDs for non-CAPsMAN wireless clients
250 String ifaceName = ifaceModel.getName();
251 String ifaceSsid = ifaceModel.getProperty(PROP_SSID_KEY);
252 if (ifaceName != null && ifaceSsid != null && !ifaceName.isBlank() && !ifaceSsid.isBlank()) {
253 this.wlanSsid.put(ifaceName, ifaceSsid);
258 private void updateCapsmanRegistrations() throws MikrotikApiException {
259 ApiConnection conn = this.connection;
263 List<Map<String, String>> response = conn.execute(CMD_PRINT_CAPSMAN_REGS);
264 if (response != null) {
265 capsmanRegistrationCache.clear();
266 response.forEach(reg -> capsmanRegistrationCache.add(new RouterosCapsmanRegistration(reg)));
270 private void updateWirelessRegistrations() throws MikrotikApiException {
271 ApiConnection conn = this.connection;
275 List<Map<String, String>> response = conn.execute(CMD_PRINT_WIRELESS_REGS);
276 wirelessRegistrationCache.clear();
277 response.forEach(props -> {
278 String wlanIfaceName = props.get("interface");
279 String wlanSsidName = wlanSsid.get(wlanIfaceName);
281 if (wlanSsidName != null && wlanIfaceName != null && !wlanIfaceName.isBlank() && !wlanSsidName.isBlank()) {
282 props.put(PROP_SSID_KEY, wlanSsidName);
284 wirelessRegistrationCache.add(new RouterosWirelessRegistration(props));
288 private void updateResources() throws MikrotikApiException {
289 ApiConnection conn = this.connection;
293 List<Map<String, String>> response = conn.execute(CMD_PRINT_RESOURCE);
294 this.resourcesCache = new RouterosSystemResources(response.get(0));
297 private void updateRouterboardInfo() throws MikrotikApiException {
298 ApiConnection conn = this.connection;
302 List<Map<String, String>> response = conn.execute(CMD_PRINT_RB_INFO);
303 this.rbInfo = new RouterosRouterboardInfo(response.get(0));