Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mikrotik] Add support for RouterOS 7 devices and support internal radios with capsman disabled #17547

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
*/
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.ThingStatus.*;
import static org.openhab.core.thing.ThingStatusDetail.CONFIGURATION_ERROR;
import static org.openhab.core.types.RefreshType.REFRESH;

Expand Down Expand Up @@ -63,8 +62,6 @@ public abstract class MikrotikBaseThingHandler<C extends ConfigValidation> exten
protected ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofDays(1), () -> false);
protected Map<String, State> currentState = new HashMap<>();

// public static boolean supportsThingType(ThingTypeUID thingTypeUID) <- in subclasses

public MikrotikBaseThingHandler(Thing thing) {
super(thing);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
*/
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.ThingStatus.*;
import static org.openhab.core.thing.ThingStatusDetail.GONE;

import java.math.BigDecimal;
Expand All @@ -31,6 +30,7 @@
import org.openhab.binding.mikrotik.internal.model.RouterosLTEInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosPPPCliInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosPPPoECliInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosWifiInterface;
import org.openhab.binding.mikrotik.internal.model.RouterosWlanInterface;
import org.openhab.binding.mikrotik.internal.util.RateCalculator;
import org.openhab.binding.mikrotik.internal.util.StateUtil;
Expand Down Expand Up @@ -184,6 +184,8 @@ protected void refreshChannel(ChannelUID channelUID) {
newState = getEtherIterfaceChannelState(channelID);
} else if (iface instanceof RouterosCapInterface) {
newState = getCapIterfaceChannelState(channelID);
} else if (iface instanceof RouterosWifiInterface) {
newState = getWifiIterfaceChannelState(channelID);
} else if (iface instanceof RouterosWlanInterface) {
newState = getWlanIterfaceChannelState(channelID);
} else if (iface instanceof RouterosPPPCliInterface) {
Expand Down Expand Up @@ -264,6 +266,26 @@ protected State getWlanIterfaceChannelState(String channelID) {
}
}

protected State getWifiIterfaceChannelState(String channelID) {
RouterosWifiInterface wlIface = (RouterosWifiInterface) 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ protected void refreshChannel(ChannelUID channelUID) {
case MikrotikBindingConstants.CHANNEL_CPU_LOAD:
newState = StateUtil.qtyPercentOrNull(rbRes.getCpuLoad());
break;
default:
logger.warn("Unimplemented channel:{}", channelID);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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.library.types.DecimalType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
Expand Down Expand Up @@ -214,11 +215,10 @@ 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());
return new DecimalType(wirelessReg.getRxSignal());
default:
return UnDefType.UNDEF;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ public class RouterosDevice {
public static final String PROP_TYPE_KEY = "type";
public static final String PROP_NAME_KEY = "name";
public static final String PROP_SSID_KEY = "ssid";

public static final String PROP_CONFIG_SSID_KEY = "configuration.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_WIFI_REGS = "/interface/wifi/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";
Expand Down Expand Up @@ -85,6 +86,8 @@ private static Optional<RouterosInterfaceBase> createTypedInterface(Map<String,
return Optional.of(new RouterosCapInterface(interfaceProps));
case WLAN:
return Optional.of(new RouterosWlanInterface(interfaceProps));
case WIFI:
return Optional.of(new RouterosWifiInterface(interfaceProps));
case PPPOE_CLIENT:
return Optional.of(new RouterosPPPoECliInterface(interfaceProps));
case PPP_CLIENT:
Expand Down Expand Up @@ -116,6 +119,9 @@ public boolean isConnected() {
public void start() throws MikrotikApiException {
login();
updateRouterboardInfo();
if (rbInfo != null) {
logger.debug("RouterOS Version = {}", rbInfo.getFirmwareVersion());
}
}

public void stop() {
Expand Down Expand Up @@ -160,8 +166,25 @@ public void refresh() throws MikrotikApiException {
synchronized (this) {
updateResources();
updateInterfaceData();
updateCapsmanRegistrations();
updateWirelessRegistrations();
try {
updateCapsmanRegistrations();
} catch (MikrotikApiException e) {
logger.debug(
"MikrotikApiException: Device may have the CAPsMAN feature for wireless management disabled.");
}
wirelessRegistrationCache.clear();
try {
updateWirelessRegistrations();
} catch (MikrotikApiException e) {
logger.debug(
"MikrotikApiException: Device does not appear to have any built in CAPsMAN wireless devices.");
}
try {
updateWifiRegistrations();
} catch (MikrotikApiException e) {
logger.debug("MikrotikApiException: Device does not appear to have any built in wifi.");
}
logger.trace("There are {} wirelessRegistration's registered in cache.", wirelessRegistrationCache.size());
}
}

Expand Down Expand Up @@ -205,6 +228,7 @@ private void updateInterfaceData() throws MikrotikApiException {
this.wlanSsid.clear();
this.interfaceCache.clear();
ifaceResponse.forEach(props -> {
logger.trace("Interface Details:{}", props.toString());
Optional<RouterosInterfaceBase> ifaceOpt = createTypedInterface(props);
if (ifaceOpt.isPresent()) {
RouterosInterfaceBase iface = ifaceOpt.get();
Expand All @@ -215,12 +239,14 @@ private void updateInterfaceData() throws MikrotikApiException {
}
});

// Checks if any new interfaces have been setup since last check
Map<String, Map<String, String>> typedIfaceResponse = new HashMap<>();
for (String ifaceApiType : interfaceTypesToPoll) {
String cmd = String.format(CMD_PRINT_IFACE_TYPE_TPL, ifaceApiType);
if (ifaceApiType.compareTo("cap") == 0) {
cmd = CMD_PRINT_CAPS_IFACES;
}
logger.debug("Command used for updating the interfaces is:{}", cmd);
connection.execute(cmd).forEach(propMap -> {
String ifaceName = propMap.get(PROP_NAME_KEY);
if (ifaceName != null) {
Expand All @@ -235,24 +261,36 @@ private void updateInterfaceData() throws MikrotikApiException {

for (RouterosInterfaceBase ifaceModel : interfaceCache) {
// Enrich with detailed data

Map<String, String> additionalIfaceProps = typedIfaceResponse.get(ifaceModel.getName());
if (additionalIfaceProps != null) {
ifaceModel.mergeProps(additionalIfaceProps);
}
// Get monitor data
// Get monitor data, runs if you have added an interface thing.
if (ifaceModel.hasMonitor() && monitoredInterfaces.contains(ifaceModel.getName())) {
String cmd = String.format(CMD_MONTOR_IFACE_MONITOR_TPL, ifaceModel.getApiType(), ifaceModel.getName());
if (logger.isDebugEnabled()) {
logger.debug("Getting detailed data for Interface:{}, with command:{}", ifaceModel.getName(), cmd);
}
List<Map<String, String>> monitorProps = connection.execute(cmd);
ifaceModel.mergeProps(monitorProps.get(0));
}
// Note SSIDs for non-CAPsMAN wireless clients
String ifaceName = ifaceModel.getName();
String ifaceSsid = ifaceModel.getProperty(PROP_SSID_KEY);
String ifaceSsid;
switch (ifaceModel.getApiType()) {
case "wifi":
ifaceSsid = ifaceModel.getProperty(PROP_CONFIG_SSID_KEY);
break;
case "caps":
case "wireless":
default:
ifaceSsid = ifaceModel.getProperty(PROP_SSID_KEY);
}
if (ifaceName != null && ifaceSsid != null && !ifaceName.isBlank() && !ifaceSsid.isBlank()) {
this.wlanSsid.put(ifaceName, ifaceSsid);
}
}
logger.debug("Found the following SSID's:{}", wlanSsid.toString());
}

private void updateCapsmanRegistrations() throws MikrotikApiException {
Expand All @@ -273,7 +311,6 @@ private void updateWirelessRegistrations() throws MikrotikApiException {
return;
}
List<Map<String, String>> response = conn.execute(CMD_PRINT_WIRELESS_REGS);
wirelessRegistrationCache.clear();
response.forEach(props -> {
String wlanIfaceName = props.get("interface");
String wlanSsidName = wlanSsid.get(wlanIfaceName);
Expand All @@ -285,6 +322,23 @@ private void updateWirelessRegistrations() throws MikrotikApiException {
});
}

private void updateWifiRegistrations() throws MikrotikApiException {
ApiConnection conn = this.connection;
if (conn == null) {
return;
}
List<Map<String, String>> response = conn.execute(CMD_PRINT_WIFI_REGS);
response.forEach(props -> {
String wlanIfaceName = props.get("interface");
String wlanSsidName = wlanSsid.get(wlanIfaceName);

if (wlanSsidName != null && wlanIfaceName != null && !wlanIfaceName.isBlank() && !wlanSsidName.isBlank()) {
props.put("configuration.ssid", wlanSsidName);
}
wirelessRegistrationCache.add(new RouterosWirelessRegistration(props));
});
}

private void updateResources() throws MikrotikApiException {
ApiConnection conn = this.connection;
if (conn == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ public enum RouterosInterfaceType {
ETHERNET("ether"),
BRIDGE("bridge"),
WLAN("wlan"),
WIFI("wifi"),
CAP("cap"),
PPP_CLIENT("ppp-out"),
PPPOE_CLIENT("pppoe-out"),
VLAN("vlan"),
VETH("veth"), // docker containers create virtual ether ports
L2TP_SERVER("l2tp-in"),
L2TP_CLIENT("l2tp-out"),
LTE("lte");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2024 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 RouterosWifiInterface} is a model class for `wifi` interface models having casting accessors for
* data that is specific to this network interface kind. Is a subclass of {@link RouterosInterfaceBase}.
*
* @author Matthew Skinner - Initial contribution
*/
@NonNullByDefault
public class RouterosWifiInterface extends RouterosWlanInterface {

public RouterosWifiInterface(Map<String, String> props) {
super(props);
}

@Override
public RouterosInterfaceType getDesignedType() {
return RouterosInterfaceType.WIFI;
}

@Override
public String getApiType() {
return "wifi";
}

@Override
public int getRegisteredClients() {
Integer registeredClients = getIntProp("registered-peers");
return registeredClients == null ? 0 : registeredClients;
}

@Override
public int getAuthorizedClients() {
Integer authedClients = getIntProp("authorized-peers");
return authedClients == null ? 0 : authedClients;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,29 @@
*/
@NonNullByDefault
public class RouterosWirelessRegistration extends RouterosRegistrationBase {

public RouterosWirelessRegistration(Map<String, String> props) {
super(props);
}

public int getRxSignal() {
String signalValue = getProp("signal-strength", "0@hz").split("@")[0];
String signalValue = getProp("signal", "0");
if (signalValue.isEmpty()) {
signalValue = getProp("signal-strength", "0@hz").split("@")[0];
} else {
int value = Integer.parseInt(signalValue);
if (value < -80) {
return 0;
} else if (value < -75) {
return 1;
} else if (value < -68) {
return 2;
} else if (value < -55) {
return 3;
} else {
return 4;
}
}
return Integer.parseInt(signalValue);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public boolean isMaster() {
}

public @Nullable String getCurrentState() {
// maybe state not status in newer version 7????
lsiepel marked this conversation as resolved.
Show resolved Hide resolved
return getProp("status");
}

Expand Down