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

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

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

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

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

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

* [mikrotik] Raw uptime channel removed

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

* [mikrotik] Traces removed

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

* [mikrotik] Readme update

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

* [mikrotik] More Null checks

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

* [mikrotik] thing-types update

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

* [mikrotik] Units update

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

* [mikrotik] Readme signal update

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

* [mikrotik] Rate channels unit fix

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

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

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

* [mikrotik] No more compiler warnings

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

* [mikrotik] Minus null check

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

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

Co-authored-by: Fabian Wolter <github@fabian-wolter.de>
This commit is contained in:
Oleg Vivtash
2021-09-21 20:40:57 +03:00
committed by GitHub
parent fd646a59bd
commit 5ae1567ba8
40 changed files with 3958 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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