<artifactId>org.openhab.binding.astro</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.binding.asuswrt</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.atlona</artifactId>
--- /dev/null
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
--- /dev/null
+# Asuswrt Binding
+
+This binding adds support to read information from ASUS-Routers (Copyright © ASUS).
+
+## Supported Things
+
+This binding supports ASUS routers with Asuswrt or [Asuswrt-Merlin](https://www.asuswrt-merlin.net/) firmware.
+Firmware 5.x.x (some DSL models) is NOT supported (not Asuswrt).
+
+| ThingType | Name | Descripion |
+|---------------|------------|--------------------------------------|
+| bridge | router | Router to which the binding connects |
+| - | interface | Network interface of the router |
+| - | client | Client is connected to the bridge |
+
+### `router` Thing Configuration
+
+| Name | Type | Description | Default | Required | Advanced |
+|-----------------|---------|---------------------------------------|---------------------|----------|----------|
+| hostname | text | Hostname or IP address of the device | router.asus.com | yes | no |
+| username | text | Username to access the device | N/A | yes | no |
+| password | text | Password to access the device | N/A | yes | no |
+| useSSL | boolean | Connect over SSL or use http:// | false | no | no |
+| refreshInterval | integer | Interval the device is polled in sec. | 20 | no | yes |
+| httpPort | integer | HTTP-Port | 80 | no | yes |
+| httpsPort | integer | HTTPS-Port | 443 | no | yes |
+
+### `interface` Thing Configuration
+
+| Name | Type | Description | Default | Required | Advanced |
+|-----------------|---------|---------------------------------------|---------------------|----------|----------|
+| interfaceName | text | options name of interface (wan/lan) | N/A | yes | no |
+
+### `client` Thing Configuration
+
+| Name | Type | Description | Default | Required | Advanced |
+|-----------------|---------|---------------------------------------|---------------------|----------|----------|
+| macAddress | text | Unique MAC address of the device | N/A | yes | no |
+| clientNick | text | Nickname used by OH | N/A | no | no |
+
+
+## Properties
+
+All devices support some of the following properties:
+
+| property | description | things supporting this channel |
+|------------------|------------------------------|---------------------------------------|
+| vendor | Vendor of device | router, client |
+| dnsName | DNS name of device | router, client |
+
+
+## Channels
+
+All devices support some of the following channels:
+
+| group | channel |type | description | things supporting this channel |
+|------------------|----------------------|------------------------|--------------------------------------------|-----------------------------------|
+| network-info | mac-address | text (RO) | HW address | interface, client |
+| | ip-address | text (RO) | IP address | interface |
+| | ip-method | text (RO) | IP method (static/dhcp) | interface, client |
+| | subnet | text (RO) | Subnetmask | interface |
+| | gateway | text (RO) | Default gateway | interface |
+| | dns-servers | text (RO) | DNS servers | interface |
+| | network-state | Switch (RO) | Client is online | interface, client |
+| | internet-state | Switch (RO) | Client connected to Internet | client |
+| sys-info | mem-total | Number:DataAmountype | Total memory in MB | router |
+| | mem-used | Number:DataAmountype | Used memory in MB | router |
+| | mem-free | Number:DataAmountype | Free memory in MB | router |
+| | mem-used-percent | Number:Dimensionles | Used memory in % | router |
+| | cpu-used-percent | Number:Dimensionles | Total CPU usage in percent over all cores | router |
+| client-list | known-clients | text (RO) | Known clients with name and MAC addresses | router |
+| | online-clients | text (RO) | Online clients with name and MAC addresses | router |
+| | online-macs | text (RO) | List with MAC addresses of online clients | router |
+| | online-clients-count | Number:Dimensionless | Count of online clients | router |
+| traffic | current-rx | Number:DataTransferRate| Current DataTransferRate MBits/s (receive) | interface, client |
+| | current-tx | Number:DataTransferRate| Current DataTransferRate MBits/s (send) | interface, client |
+| | today-rx | Number:DataAmount | Data received since 0:00 a clock in MB | interface, client |
+| | today-tx | Number:DataAmount | Data sent since 0:00 a clock in MB | interface, client |
+| | total-rx | Number:DataAmount | Data received since reboot in MB | interface, client |
+| | total-tx | Number:DataAmount | Data sent since reboot in MB | interface, client |
+
+
+## Events
+
+All devices support some of the following Events:
+
+| group | event |kind | description | things supporting this event |
+|------------------|---------------------|------------|------------------------------------------------------------------------|---------------------------------|
+| network-info | connection-event | Trigger | Fired if connection is established ('connected') or ('disconnected') | interface |
+| | client-online-event | Trigger | Fired if client leaves ('gone') or enters ('connected') the network | client |
+| client-list | client-online-event | Trigger | Fired if client leaves ('gone') or enters ('connected') the network | router |
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+ <version>4.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.openhab.binding.asuswrt</artifactId>
+
+ <name>openHAB Add-ons :: Bundles :: Asuswrt Binding</name>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.asuswrt-${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-asuswrt" description="Asuswrt Binding" version="${project.version}">
+ <feature>openhab-runtime-base</feature>
+ <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.asuswrt/${project.version}</bundle>
+ </feature>
+</features>
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.*;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtClientInfo;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtClientList;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtInterfaceList;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtIpInfo;
+import org.openhab.binding.asuswrt.internal.things.AsuswrtRouter;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AsuswrtDiscoveryService} is responsible for discovering clients.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtDiscoveryService.class);
+ private String uid = "";
+ protected @NonNullByDefault({}) AsuswrtRouter router;
+
+ public AsuswrtDiscoveryService() {
+ super(SUPPORTED_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_S, false);
+ }
+
+ @Override
+ public void activate() {
+ }
+
+ @Override
+ public void deactivate() {
+ super.deactivate();
+ removeAllResults();
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof AsuswrtRouter router) {
+ router.setDiscoveryService(this);
+ this.router = router;
+ this.uid = router.getUID().getAsString();
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return this.router;
+ }
+
+ /*
+ * Scan handling
+ */
+
+ /**
+ * Starts a manual scan.
+ */
+ @Override
+ public void startScan() {
+ logger.trace("{} starting scan", uid);
+ if (router != null) {
+ /* query Data */
+ router.queryDeviceData(false);
+ /* discover interfaces */
+ AsuswrtInterfaceList ifList = router.getInterfaces();
+ handleInterfaceScan(ifList);
+ /* discover clients */
+ AsuswrtClientList clientList = router.getClients();
+ handleClientScan(clientList);
+ }
+ }
+
+ @Override
+ public void stopScan() {
+ super.stopScan();
+ removeOlderResults(getTimestampOfLastScan());
+ }
+
+ /**
+ * Removes all scan results.
+ */
+ private void removeAllResults() {
+ removeOlderResults(new Date().getTime());
+ }
+
+ /**
+ * Creates {@link DiscoveryResult}s from the provided {@link AsuswrtInterfaceList}.
+ */
+ private void handleInterfaceScan(AsuswrtInterfaceList ifList) {
+ try {
+ for (AsuswrtIpInfo ifInfo : ifList) {
+ DiscoveryResult discoveryResult = createInterfaceResult(ifInfo);
+ thingDiscovered(discoveryResult);
+ }
+ } catch (Exception e) {
+ logger.debug("Error while handling interface scan reults", e);
+ }
+ }
+
+ /**
+ * Creates {@link DiscoveryResult}s from the provided {@link AsuswrtClientList}.
+ */
+ public void handleClientScan(AsuswrtClientList clientList) {
+ try {
+ for (AsuswrtClientInfo client : clientList) {
+ DiscoveryResult discoveryResult = createClientResult(client);
+ thingDiscovered(discoveryResult);
+ }
+ } catch (Exception e) {
+ logger.debug("Error while handling client scan results", e);
+ }
+ }
+
+ /*
+ * Discovery result creation
+ */
+
+ /**
+ * Creates a {@link DiscoveryResult} from the provided {@link AsuswrtIpInfo}.
+ */
+ private DiscoveryResult createInterfaceResult(AsuswrtIpInfo interfaceInfo) {
+ String ifName = interfaceInfo.getName();
+ String macAddress = interfaceInfo.getMAC();
+ String label = "AwrtInterface_" + ifName;
+
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(NETWORK_REPRESENTATION_PROPERTY, ifName);
+ properties.put(Thing.PROPERTY_MAC_ADDRESS, macAddress);
+
+ logger.debug("{} thing discovered: '{}", uid, label);
+ if (this.router != null) {
+ ThingUID bridgeUID = router.getUID();
+ ThingUID thingUID = new ThingUID(THING_TYPE_INTERFACE, bridgeUID, ifName);
+ return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withRepresentationProperty(NETWORK_REPRESENTATION_PROPERTY).withBridge(bridgeUID).withLabel(label)
+ .build();
+ } else {
+ ThingUID thingUID = new ThingUID(BINDING_ID, ifName);
+ return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withRepresentationProperty(NETWORK_REPRESENTATION_PROPERTY).withLabel(label).build();
+ }
+ }
+
+ /**
+ * Creates a {@link DiscoveryResult} from the provided {@link AsuswrtClientInfo}.
+ */
+ private DiscoveryResult createClientResult(AsuswrtClientInfo clientInfo) {
+ String macAddress = clientInfo.getMac();
+ String unformatedMac = unformatMac(macAddress);
+ String clientName;
+ String nickName;
+ String label = "AwrtClient_";
+
+ // Create label and thing names
+ clientName = stringOrDefault(clientInfo.getName(), "client_" + unformatedMac);
+ nickName = stringOrDefault(clientInfo.getNickName(), clientName);
+ if (nickName.equals(clientName)) {
+ label += nickName;
+ } else {
+ label += nickName + " (" + clientName + ")";
+ }
+
+ // Create properties
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(Thing.PROPERTY_MAC_ADDRESS, macAddress);
+ properties.put(Thing.PROPERTY_VENDOR, clientInfo.getVendor());
+ properties.put(PROPERTY_CLIENT_NAME, clientName);
+ properties.put(CHANNEL_CLIENT_NICKNAME, nickName);
+
+ logger.debug("{} thing discovered: '{}", uid, label);
+ if (this.router != null) {
+ ThingUID bridgeUID = router.getUID();
+ ThingUID thingUID = new ThingUID(THING_TYPE_CLIENT, bridgeUID, unformatedMac);
+ return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withRepresentationProperty(CLIENT_REPRESENTATION_PROPERTY).withTTL(DISCOVERY_AUTOREMOVE_S)
+ .withBridge(bridgeUID).withLabel(label).build();
+ } else {
+ ThingUID thingUID = new ThingUID(BINDING_ID, unformatedMac);
+ return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withRepresentationProperty(CLIENT_REPRESENTATION_PROPERTY).withTTL(DISCOVERY_AUTOREMOVE_S)
+ .withLabel(label).build();
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtErrorConstants.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.openhab.binding.asuswrt.internal.things.AsuswrtClient;
+import org.openhab.binding.asuswrt.internal.things.AsuswrtInterface;
+import org.openhab.binding.asuswrt.internal.things.AsuswrtRouter;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+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.ComponentContext;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AsuswrtHandlerFactory} is responsible for creating things and thing handlers.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.asuswrt", service = ThingHandlerFactory.class)
+public class AsuswrtHandlerFactory extends BaseThingHandlerFactory {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtHandlerFactory.class);
+ private final Set<AsuswrtRouter> routerHandlers = new HashSet<>();
+ private final HttpClient httpClient;
+
+ public AsuswrtHandlerFactory() {
+ // Set SslContextfactory
+ SslContextFactory sslContextFactory = new SslContextFactory.Client();
+ if (HTTP_SSL_TRUST_ALL) {
+ sslContextFactory.setTrustAll(true);
+ sslContextFactory.setEndpointIdentificationAlgorithm(null);
+ }
+ // Create new httpClient
+ httpClient = new HttpClient(sslContextFactory);
+ httpClient.setFollowRedirects(false);
+ httpClient.setMaxConnectionsPerDestination(HTTP_MAX_CONNECTIONS);
+ httpClient.setMaxRequestsQueuedPerDestination(HTTP_MAX_QUEUED_REQUESTS);
+ try {
+ httpClient.start();
+ } catch (Exception e) {
+ logger.error(ERR_HTTP_CLIENT_FAILED);
+ }
+ }
+
+ @Deactivate
+ @Override
+ protected void deactivate(ComponentContext componentContext) {
+ super.deactivate(componentContext);
+ try {
+ httpClient.stop();
+ } catch (Exception e) {
+ logger.debug("Unable to stop httpClient");
+ }
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (THING_TYPE_ROUTER.equals(thingTypeUID)) {
+ AsuswrtRouter router = new AsuswrtRouter((Bridge) thing, this.httpClient);
+ routerHandlers.add(router);
+ return router;
+ } else if (THING_TYPE_CLIENT.equals(thingTypeUID)) {
+ AsuswrtRouter router = getRouter(thing);
+ if (router != null) {
+ return new AsuswrtClient(thing, router);
+ }
+ } else if (THING_TYPE_INTERFACE.equals(thingTypeUID)) {
+ AsuswrtRouter router = getRouter(thing);
+ if (router != null) {
+ return new AsuswrtInterface(thing, router);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the {@link AsuswrtRouter} handler (Bridge) from a Thing.
+ */
+ protected @Nullable AsuswrtRouter getRouter(Thing thing) {
+ ThingUID bridgeUID = thing.getBridgeUID();
+ if (bridgeUID != null) {
+ for (AsuswrtRouter router : routerHandlers) {
+ if (bridgeUID.equals(router.getUID())) {
+ return router;
+ }
+ }
+ }
+ logger.warn(ERR_BRIDGE_NOT_DECLARED);
+ return null;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.api;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtErrorConstants.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.getValueOrDefault;
+
+import java.net.NoRouteToHostException;
+import java.util.concurrent.TimeoutException;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLKeyException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtConfiguration;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtCredentials;
+import org.openhab.binding.asuswrt.internal.things.AsuswrtRouter;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtConnector} is a {@link AsuswrtHttpClient} that also keeps track of router configuration and
+ * credentials.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtConnector extends AsuswrtHttpClient {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtConnector.class);
+ private AsuswrtCredentials credentials;
+ private AsuswrtConfiguration routerConfig;
+ protected Long lastQuery = 0L;
+
+ public AsuswrtConnector(AsuswrtRouter router) {
+ super(router);
+ routerConfig = router.getConfiguration();
+ this.credentials = new AsuswrtCredentials(routerConfig);
+ }
+
+ /*
+ * Connector commands
+ */
+
+ /**
+ * Login to the router.
+ */
+ public Boolean login() {
+ String url = getURL("login.cgi");
+ String encodedCredentials = credentials.getEncodedCredentials();
+ String payload = "";
+
+ logout(); // logout (unset cookie) first
+ router.errorHandler.reset();
+
+ logger.trace("({}) perform login to '{}' with '{}'", uid, url, encodedCredentials);
+
+ payload = "login_authorization=" + encodedCredentials + "}";
+ ContentResponse response = getSyncRequest(url, payload);
+ if (response != null) {
+ setCookieFromResponse(response);
+ }
+ if (cookieStore.isValid()) {
+ router.setState(ThingStatus.ONLINE);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Logout and unsets the cookie.
+ */
+ public void logout() {
+ this.cookieStore.resetCookie();
+ }
+
+ /**
+ * Gets system information from the device.
+ */
+ public void querySysInfo(boolean asyncRequest) {
+ queryDeviceData(CMD_GET_SYSINFO, asyncRequest);
+ }
+
+ /**
+ * Queries data from the device.
+ *
+ * @param command command constant to sent
+ * @param asyncRequest <code>true</code> if request should be sent asynchronous, <code>false</code> if synchronous
+ */
+ public void queryDeviceData(String command, boolean asyncRequest) {
+ logger.trace("({}) queryDeviceData", uid);
+ Long now = System.currentTimeMillis();
+
+ router.errorHandler.reset();
+ if (cookieStore.cookieIsExpired()) {
+ login();
+ }
+
+ if (now > this.lastQuery + HTTP_QUERY_MIN_GAP_MS) {
+ String url = getURL("appGet.cgi");
+ String payload = "hook=" + command;
+ this.lastQuery = now;
+
+ // Send payload as url parameter
+ url = url + "?" + payload;
+ url = url.replace(";", "%3B");
+
+ // Send asynchronous or synchronous HTTP request
+ if (asyncRequest) {
+ sendAsyncRequest(url, payload, command);
+ } else {
+ sendSyncRequest(url, payload, command);
+ }
+ } else {
+ logger.trace("({}) query skipped cause of min_gap: {} <- {}", uid, now, lastQuery);
+ }
+ }
+
+ /*
+ * Response handling
+ */
+
+ /**
+ * Handle successful HTTP response by delegating to the connector class.
+ *
+ * @param responseBody response body as string
+ * @param command command constant which was sent
+ */
+ @Override
+ protected void handleHttpSuccessResponse(String responseBody, String command) {
+ JsonObject jsonObject = getJsonFromString(responseBody);
+ router.dataReceived(jsonObject, command);
+ }
+
+ /**
+ * Handles HTTP result failures.
+ *
+ * @param e Throwable exception
+ * @param payload full payload for debugging
+ */
+ @Override
+ protected void handleHttpResultError(Throwable e, String payload) {
+ super.handleHttpResultError(e, payload);
+ String errorMessage = getValueOrDefault(e.getMessage(), "");
+
+ if (e instanceof TimeoutException || e instanceof NoRouteToHostException) {
+ router.errorHandler.raiseError(ERR_CONN_TIMEOUT, errorMessage);
+ router.setState(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
+ } else if (e instanceof SSLException || e instanceof SSLKeyException || e instanceof SSLHandshakeException) {
+ router.errorHandler.raiseError(ERR_SSL_EXCEPTION, payload);
+ router.setState(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
+ } else if (e instanceof InterruptedException) {
+ router.errorHandler.raiseError(new Exception(e), payload);
+ router.setState(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
+ } else {
+ router.errorHandler.raiseError(new Exception(e), errorMessage);
+ router.setState(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
+ }
+ }
+
+ /*
+ * Other
+ */
+
+ /**
+ * Gets the target URL.
+ */
+ protected String getURL(String site) {
+ String url = routerConfig.hostname;
+ if (routerConfig.useSSL) {
+ url = HTTPS_PROTOCOL + url;
+ if (routerConfig.httpsPort != 443) {
+ url = url + ":" + routerConfig.httpsPort;
+ }
+ } else {
+ url = HTTP_PROTOCOL + url;
+ if (routerConfig.httpPort != 80) {
+ url = url + ":" + routerConfig.httpPort;
+ }
+ }
+ return url + "/" + site;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.api;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.COOKIE_LIFETIME_S;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link AsuswrtCookie} is used for storing cookie details.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtCookie {
+ protected String cookie = "";
+ protected String token = "";
+ protected Long cookieTimeStamp = 0L;
+
+ /*
+ * Set and reset functions
+ */
+
+ /**
+ * Sets a new cookie.
+ */
+ public void setCookie(String cookie) {
+ this.cookie = cookie;
+ cookieTimeStamp = System.currentTimeMillis();
+ }
+
+ /**
+ * Resets a cookie.
+ */
+ public void resetCookie() {
+ cookie = "";
+ token = "";
+ cookieTimeStamp = 0L;
+ }
+
+ /*
+ * Cookie checks
+ */
+
+ /**
+ * Checks if a cookie is set.
+ */
+ public boolean cookieIsSet() {
+ return !cookie.isBlank();
+ }
+
+ /**
+ * Checks if a cookie is expired.
+ *
+ * @return <code>true</code> if cookie is set and expired
+ */
+ public boolean cookieIsExpired() {
+ return cookieTimeStamp > 0L && System.currentTimeMillis() > cookieTimeStamp + (COOKIE_LIFETIME_S * 1000);
+ }
+
+ /**
+ * Checks if a cookie is set and not expired.
+ */
+ public boolean isValid() {
+ return !cookieIsExpired() && cookieIsSet();
+ }
+
+ /**
+ * Gets the cookie.
+ */
+ public String getCookie() {
+ return cookie;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.api;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.JSON_MEMBER_TOKEN;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtErrorConstants.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.getValueOrDefault;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpResponse;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Result;
+import org.eclipse.jetty.client.util.BufferingResponseListener;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils;
+import org.openhab.binding.asuswrt.internal.things.AsuswrtRouter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtHttpClient} is used for (a)synchronous HTTP requests.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtHttpClient {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtHttpClient.class);
+ private Gson gson = new Gson();
+ protected AsuswrtRouter router;
+ protected final String uid;
+ public AsuswrtCookie cookieStore = new AsuswrtCookie();
+
+ public AsuswrtHttpClient(AsuswrtRouter router) {
+ this.router = router;
+ uid = router.getUID().toString();
+ }
+
+ /*
+ * HTTP actions
+ */
+
+ /**
+ * Sends a synchronous HTTP request.
+ *
+ * The result will be handled in {@link #handleHttpSuccessResponse(String, String) or
+ * {@link #handleHttpResultError(Throwable)}.
+ *
+ * If the response should be returned use {@link #getSyncRequest(String, String)} instead.
+ *
+ * @param url the URL the request is sent to
+ * @param payload the payload to send
+ * @param command the command to perform
+ */
+ protected void sendSyncRequest(String url, String payload, String command) {
+ ContentResponse response = getSyncRequest(url, payload);
+ if (response != null) {
+ handleHttpSuccessResponse(response.getContentAsString(), command);
+ }
+ }
+
+ /**
+ * Sends a synchronous HTTP request.
+ *
+ * @param url the URL the request is sent to
+ * @param payload the payload to send
+ * @return {@link ContentResponse} of the request
+ */
+ protected @Nullable ContentResponse getSyncRequest(String url, String payload) {
+ logger.trace("({}) sendRequest '{}' to '{}' with cookie '{}'", uid, payload, url, cookieStore.getCookie());
+ Request httpRequest = this.router.getHttpClient().newRequest(url).method(HttpMethod.POST.toString());
+
+ // Set header
+ httpRequest = setHeaders(httpRequest);
+ httpRequest.timeout(HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+ // Add request body
+ httpRequest.content(new StringContentProvider(payload, HTTP_CONTENT_CHARSET), HTTP_CONTENT_TYPE);
+ try {
+ return httpRequest.send();
+ } catch (Exception e) {
+ handleHttpResultError(e);
+ }
+ return null;
+ }
+
+ /**
+ * Sends an asynchronous HTTP request so it does not wait for an answer.
+ *
+ * The result will be handled in {@link #handleHttpSuccessResponse(String, String) or
+ * {@link #handleHttpResultError(Throwable)}.
+ *
+ * @param url the URL to which the request is sent to
+ * @param payload the payload data
+ * @param command command to execute, this will handle ResponseType
+ */
+ protected void sendAsyncRequest(String url, String payload, String command) {
+ logger.trace("({}) sendAsyncRequest to '{}' with cookie '{}'", uid, url, cookieStore.getCookie());
+ try {
+ Request httpRequest = router.getHttpClient().newRequest(url).method(HttpMethod.POST.toString());
+
+ // Set header
+ httpRequest = setHeaders(httpRequest);
+
+ // Add request body
+ httpRequest.content(new StringContentProvider(payload, HTTP_CONTENT_CHARSET), HTTP_CONTENT_TYPE);
+
+ httpRequest.timeout(HTTP_TIMEOUT_MS, TimeUnit.MILLISECONDS).send(new BufferingResponseListener() {
+ @NonNullByDefault({})
+ @Override
+ public void onComplete(Result result) {
+ final HttpResponse response = (HttpResponse) result.getResponse();
+ if (result.getFailure() != null) {
+ // Handle result errors
+ handleHttpResultError(result.getFailure());
+ } else if (response.getStatus() != 200) {
+ logger.debug("({}) sendAsyncRequest response error '{}'", uid, response.getStatus());
+ router.errorHandler.raiseError(ERR_RESPONSE, getContentAsString());
+ } else {
+ // Request successful
+ String rBody = getContentAsString();
+ logger.trace("({}) requestCompleted '{}'", uid, rBody);
+ // Handle result
+ handleHttpSuccessResponse(rBody, command);
+ }
+ }
+ });
+ } catch (Exception e) {
+ router.errorHandler.raiseError(e);
+ }
+ }
+
+ /**
+ * Sets HTTP headers.
+ */
+ private Request setHeaders(Request httpRequest) {
+ // Set header
+ httpRequest.header("content-type", HTTP_CONTENT_TYPE);
+ httpRequest.header("user-agent", HTTP_USER_AGENT);
+ if (cookieStore.isValid()) {
+ httpRequest.header("cookie", cookieStore.getCookie());
+ }
+ return httpRequest;
+ }
+
+ /*
+ * Response handling
+ */
+
+ /**
+ * Handles HTTP result failures.
+ *
+ * @param e the exception
+ * @param payload full payload for debugging
+ */
+ protected void handleHttpResultError(Throwable e, String payload) {
+ String errorMessage = getValueOrDefault(e.getMessage(), "");
+
+ if (e instanceof TimeoutException) {
+ logger.debug("({}) sendAsyncRequest timeout'{}'", uid, errorMessage);
+ } else if (e instanceof InterruptedException) {
+ logger.debug("({}) sending request interrupted: {}", uid, e.toString());
+ } else {
+ logger.debug("({}) sendAsyncRequest failed'{}'", uid, errorMessage);
+ }
+ }
+
+ protected void handleHttpResultError(Throwable e) {
+ handleHttpResultError(e, "");
+ }
+
+ /**
+ * Handles a successful HTTP response.
+ *
+ * @param responseBody response body as string
+ * @param command command constant which was sent
+ */
+ protected void handleHttpSuccessResponse(String responseBody, String command) {
+ }
+
+ /**
+ * Sets a cookie from a response.
+ */
+ protected void setCookieFromResponse(ContentResponse response) {
+ cookieStore.resetCookie();
+ if (response.getStatus() == 200) {
+ String rBody = response.getContentAsString();
+ logger.trace("({}) received response '{}'", uid, rBody);
+ try {
+ /* get json object 'asus_token' */
+ JsonObject jsonObject = gson.fromJson(rBody, JsonObject.class);
+ if (jsonObject != null && jsonObject.has(JSON_MEMBER_TOKEN)) {
+ String token = jsonObject.get(JSON_MEMBER_TOKEN).getAsString();
+ this.cookieStore.setCookie("asus_token=" + token);
+ }
+ } catch (Exception e) {
+ logger.debug("({}) {} on login request '{}'", uid, ERR_RESPONSE, e.getMessage());
+ router.errorHandler.raiseError(ERR_RESPONSE, e.getMessage());
+ }
+ } else {
+ String reason = AsuswrtUtils.getValueOrDefault(response.getReason(), "");
+ router.errorHandler.raiseError(ERR_RESPONSE, reason);
+ }
+ }
+
+ /**
+ * Gets JSON from a response.
+ */
+ protected JsonObject getJsonFromResponse(ContentResponse response) {
+ return getJsonFromString(response.getContentAsString());
+ }
+
+ /**
+ * Gets JSON from a response.
+ */
+ protected JsonObject getJsonFromString(String responseBody) {
+ try {
+ JsonObject jsonObject = gson.fromJson(responseBody, JsonObject.class);
+ logger.trace("({}) received result: {}", uid, responseBody);
+ /* get error code (0=success) */
+ if (jsonObject != null) {
+ return jsonObject;
+ }
+ } catch (Exception e) {
+ logger.debug("({}) {} {}", uid, ERR_JSON_FORMAT, responseBody);
+ router.getErrorHandler().raiseError(e);
+ }
+ return new JsonObject();
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.constants;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link AsuswrtBindingConstants} class defines common constants, which are used across the whole binding.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtBindingConstants {
+
+ public static final String BINDING_ID = "asuswrt";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_ROUTER = new ThingTypeUID(BINDING_ID, "router");
+ public static final ThingTypeUID THING_TYPE_CLIENT = new ThingTypeUID(BINDING_ID, "client");
+ public static final ThingTypeUID THING_TYPE_INTERFACE = new ThingTypeUID(BINDING_ID, "interface");
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ROUTER, THING_TYPE_CLIENT,
+ THING_TYPE_INTERFACE);
+
+ // Things with channel groups
+ public static final Set<ThingTypeUID> CHANNEL_GROUP_THING_SET = Collections
+ .unmodifiableSet(Stream.of(SUPPORTED_THING_TYPES_UIDS).flatMap(Set::stream).collect(Collectors.toSet()));
+
+ /*
+ * Channel lists
+ * Item channel names
+ */
+
+ // General event constants
+ public static final String EVENT_STATE_CONNECTED = "connected";
+ public static final String EVENT_STATE_GONE = "gone";
+ public static final String EVENT_STATE_DISCONNECTED = "disconnected";
+
+ // Global channels
+ public static final String CHANNELS_ALL = "any-channel";
+
+ // Channel group system info
+ public static final String CHANNEL_GROUP_SYSINFO = "sys-info";
+ public static final String CHANNEL_MEM_FREE = "mem-free";
+ public static final String CHANNEL_MEM_FREE_PERCENT = "mem-free-percent";
+ public static final String CHANNEL_MEM_TOTAL = "mem-total";
+ public static final String CHANNEL_MEM_USED = "mem-used";
+ public static final String CHANNEL_MEM_USED_PERCENT = "mem-used-percent";
+ public static final String CHANNEL_CPU_USED_PERCENT = "cpu-used-percent";
+
+ // Channel group interface information
+ public static final String CHANNEL_GROUP_NETWORK = "network-info";
+ public static final String CHANNEL_NETWORK_IP = "ip-address";
+ public static final String CHANNEL_NETWORK_MAC = "mac-address";
+ public static final String CHANNEL_NETWORK_MASK = "subnet";
+ public static final String CHANNEL_NETWORK_GATEWAY = "gateway";
+ public static final String CHANNEL_NETWORK_METHOD = "ip-method";
+ public static final String CHANNEL_NETWORK_DNS = "dns-servers";
+ public static final String CHANNEL_NETWORK_STATE = "network-state";
+ public static final String CHANNEL_NETWORK_INTERNET = "internet-state";
+ public static final String EVENT_CONNECTION = "connection-event";
+
+ // Channel group clientList information
+ public static final String CHANNEL_GROUP_CLIENTS = "client-list";
+ public static final String CHANNEL_CLIENTS_KNOWN = "known-clients";
+ public static final String CHANNEL_CLIENTS_ONLINE = "online-clients";
+ public static final String CHANNEL_CLIENTS_COUNT = "online-clients-count";
+ public static final String CHANNEL_CLIENTS_ONLINE_MAC = "online-macs";
+ public static final String EVENT_CLIENT_CONNECTION = "client-online-event";
+
+ // Channel group client information
+ public static final String CHANNEL_GROUP_CLIENT = "client";
+ public static final String CHANNEL_CLIENT_NICKNAME = "client-name";
+
+ // Channel group traffic
+ public static final String CHANNEL_GROUP_TRAFFIC = "traffic";
+ public static final String CHANNEL_TRAFFIC_TOTAL_RX = "total-rx";
+ public static final String CHANNEL_TRAFFIC_TOTAL_TX = "total-tx";
+ public static final String CHANNEL_TRAFFIC_TODAY_RX = "today-rx";
+ public static final String CHANNEL_TRAFFIC_TODAY_TX = "today-tx";
+ public static final String CHANNEL_TRAFFIC_CURRENT_RX = "current-rx";
+ public static final String CHANNEL_TRAFFIC_CURRENT_TX = "current-tx";
+
+ /*
+ * Properties
+ */
+
+ // Interface
+ public static final String PROPERTY_INTERFACE_NAME = "interfaceName";
+ public static final String NETWORK_REPRESENTATION_PROPERTY = "interfaceName";
+ // client
+ public static final String PROPERTY_CLIENT_NAME = "dnsName";
+ public static final String CLIENT_REPRESENTATION_PROPERTY = "macAddress";
+
+ /*
+ * JSON request member names
+ * Member names of JSON response
+ */
+ public static final String JSON_MEMBER_TOKEN = "asus_token";
+ // sysInfo
+ public static final String JSON_MEMBER_PRODUCTID = "productid";
+ public static final String JSON_MEMBER_FIRMWARE = "firmver";
+ public static final String JSON_MEMBER_BUILD = "buildno";
+ public static final String JSON_MEMBER_EXTENDNO = "extendo";
+ public static final String JSON_MEMBER_MAC = "lan_hwaddr";
+
+ // lanInfo
+ public static final String JSON_MEMBER_LAN_IP = "lan_ipaddr";
+ public static final String JSON_MEMBER_LAN_GATEWAY = "lan_gateway";
+ public static final String JSON_MEMBER_LAN_NETMASK = "lan_netmask";
+ public static final String JSON_MEMBER_LAN_PROTO = "lan_proto";
+
+ // wanInfo
+ public static final String JSON_MEMBER_WAN_IP = "wanlink-ipaddr";
+ public static final String JSON_MEMBER_WAN_GATEWAY = "wanlink-gateway";
+ public static final String JSON_MEMBER_WAN_NETMASK = "wanlink-netmask";
+ public static final String JSON_MEMBER_WAN_PROTO = "wanlink-type";
+ public static final String JSON_MEMBER_WAN_DNS_SERVER = "wanlink-dns";
+ public static final String JSON_MEMBER_WAN_CONNECTED = "wanlink-status";
+
+ // clientInfo
+ public static final String JSON_MEMBER_CLIENTS = "get_clientlist";
+ public static final String JSON_MEMBER_MACLIST = "maclist";
+ public static final String JSON_MEMBER_API_LEVEL = "ClientAPILevel";
+ public static final String JSON_MEMBER_CLIENT_RXCUR = "curRx";
+ public static final String JSON_MEMBER_CLIENT_TXCUR = "curTx";
+ public static final String JSON_MEMBER_CLIENT_DEFTYPE = "defaultType";
+ public static final String JSON_MEMBER_CLIENT_DPIDEVICE = "dpiDevice";
+ public static final String JSON_MEMBER_CLIENT_DPITYPE = "dpiType";
+ public static final String JSON_MEMBER_CLIENT_IPFROM = "from";
+ public static final String JSON_MEMBER_CLIENT_GROUP = "group";
+ public static final String JSON_MEMBER_CLIENT_INETMODE = "internetMode";
+ public static final String JSON_MEMBER_CLIENT_INETSTATE = "internet-state";
+ public static final String JSON_MEMBER_CLIENT_IP = "ip";
+ public static final String JSON_MEMBER_CLIENT_IPMETHOD = "ip-method";
+ public static final String JSON_MEMBER_CLIENT_IPGATEWAY = "isGateway";
+ public static final String JSON_MEMBER_CLIENT_GN = "isGN";
+ public static final String JSON_MEMBER_CLIENT_ITUNES = "isITunes";
+ public static final String JSON_MEMBER_CLIENT_LOGIN = "isLogin";
+ public static final String JSON_MEMBER_CLIENT_ONLINE = "isOnline";
+ public static final String JSON_MEMBER_CLIENT_PRINTER = "isPrinter";
+ public static final String JSON_MEMBER_CLIENT_WEBSRV = "isWebServer";
+ public static final String JSON_MEMBER_CLIENT_WIFI = "isWL";
+ public static final String JSON_MEMBER_CLIENT_KEEPARP = "keeparp";
+ public static final String JSON_MEMBER_CLIENT_MAC = "mac";
+ public static final String JSON_MEMBER_CLIENT_MACREPEAT = "macRepeat";
+ public static final String JSON_MEMBER_CLIENT_NAME = "name";
+ public static final String JSON_MEMBER_CLIENT_NICK = "nickName";
+ public static final String JSON_MEMBER_CLIENT_MODE = "opMode";
+ public static final String JSON_MEMBER_CLIENT_QOSLVL = "qosLevel";
+ public static final String JSON_MEMBER_CLIENT_ROG = "ROG";
+ public static final String JSON_MEMBER_CLIENT_RSSI = "rssi";
+ public static final String JSON_MEMBER_CLIENT_SSID = "ssid";
+ public static final String JSON_MEMBER_CLIENT_RXTOTAL = "totalRx";
+ public static final String JSON_MEMBER_CLIENT_TXTOTAL = "totalTx";
+ public static final String JSON_MEMBER_CLIENT_VENDOR = "vendor";
+ public static final String JSON_MEMBER_CLIENT_CONNECTTIME = "wlConnectTime";
+ public static final String JSON_MEMBER_CLIENT_WTFAST = "wtfast";
+
+ // usage
+ public static final String JSON_MEMBER_CPU_USAGE = "cpu_usage";
+ public static final String JSON_MEMBER_CPU_TOTAL = "cpu{x}_total";
+ public static final String JSON_MEMBER_CPU_USED = "cpu{x}_usage";
+ public static final String JSON_MEMBER_MEM_USAGE = "memory_usage";
+ public static final String JSON_MEMBER_MEM_TOTAL = "mem_total";
+ public static final String JSON_MEMBER_MEM_USED = "mem_used";
+ public static final String JSON_MEMBER_MEM_FREE = "mem_free";
+ public static final Integer USAGE_CPU_COUNT = 4; // max count of CPU cores
+
+ // traffic
+ public static final String JSON_MEMBER_TRAFFIC = "netdev";
+ public static final String JSON_MEMBER_INET_RX = "INTERNET_rx";
+ public static final String JSON_MEMBER_INET_TX = "INTERNET_tx";
+ public static final String JSON_MEMBER_LAN_RX = "WIRED_rx";
+ public static final String JSON_MEMBER_LAN_TX = "WIRED_tx";
+ public static final String JSON_MEMBER_WLAN_RX = "WIRELESS{}_rx";
+ public static final String JSON_MEMBER_WLAN_TX = "WIRELESS{}_tx";
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.constants;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link AsuswrtBindingSettings} class defines common settings constants, which are used across the whole binding.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtBindingSettings {
+
+ // Binding settings
+ public static final Integer HTTP_MAX_CONNECTIONS = 10; // setMaxConnectionsPerDestination for HTTP-Client
+ public static final Integer HTTP_MAX_QUEUED_REQUESTS = 10; // setMaxRequestsQueuedPerDestination for HTTP-Client
+ public static final Integer HTTP_TIMEOUT_MS = 5000; // http request timeout
+ public static final Integer HTTP_QUERY_MIN_GAP_MS = 5000; // http minimun gap between query data requests
+ public static final String HTTP_CONTENT_TYPE = "application/x-www-form-urlencoded";
+ public static final String HTTP_USER_AGENT = "asusrouter-Android-DUTUtil-1.0.0.3.58-163";
+ public static final String HTTP_CONTENT_CHARSET = "utf-8";
+ public static final String HTTP_PROTOCOL = "http://";
+ public static final String HTTPS_PROTOCOL = "https://";
+ public static final Boolean HTTP_SSL_TRUST_ALL = true; // trust all ssl-certs
+
+ public static final Integer COOKIE_LIFETIME_S = 3600; // lifetime of login-cookie
+ public static final Integer POLLING_INTERVAL_S_MIN = 5; // minimum polling interval
+ public static final Integer POLLING_INTERVAL_S_DEFAULT = 20; // default polling interval
+ public static final Integer RECONNECT_INTERVAL_S = 30; // interval trying try to reconnect to router
+ public static final Integer DISCOVERY_TIMEOUT_S = 10; // discovery service timeout in s
+ public static final Integer DISCOVERY_AUTOREMOVE_S = 1800; // discovery service remove things after x seconds
+
+ // List of device commands
+ public static final String CMD_GET_SYSINFO = "nvram_get(productid);nvram_get(firmver);nvram_get(buildno);nvram_get(extendno);nvram_get(lan_hwaddr);";
+ public static final String CMD_GET_LANINFO = "nvram_get(lan_hwaddr);nvram_get(lan_ipaddr);nvram_get(lan_proto);nvram_get(lan_netmask);nvram_get(lan_gateway);";
+ public static final String CMD_GET_WANINFO = "wanlink(status);wanlink(type);wanlink(ipaddr);wanlink(netmask);wanlink(gateway);wanlink(dns);wanlink(lease);wanlink(expires);";
+ public static final String CMD_GET_CLIENTLIST = "get_clientlist();";
+ public static final String CMD_GET_TRAFFIC = "netdev(appobj);";
+ public static final String CMD_GET_UPTIME = "uptime();";
+ public static final String CMD_GET_USAGE = "cpu_usage(appobj);memory_usage(appobj);";
+ public static final String CMD_GET_MEMUSAGE = "memory_usage(appobj);";
+ public static final String CMD_GET_CPUUSAGE = "cpu_usage(appobj);";
+
+ // List of interfaces
+ public static final String INTERFACE_WAN = "wan";
+ public static final String INTERFACE_LAN = "lan";
+ public static final String INTERFACE_WLAN = "wlan";
+ public static final String INTERFACE_CLIENT = "client";
+ public static final Set<String> INTERFACE_LIST = Set.of(INTERFACE_WAN, INTERFACE_LAN);
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.constants;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link AsuswrtErrorConstants} class defines error constants, which are used across the whole binding.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtErrorConstants {
+
+ public static final String ERR_HTTP_CLIENT_FAILED = "Starting 'httpClient' failed";
+ public static final String ERR_CONN_TIMEOUT = "Connection timeout";
+ public static final String ERR_RESPONSE = "Response not okay";
+ public static final String ERR_JSON_FORMAT = "Unexpected or malfomrated JSON response";
+ public static final String ERR_JSON_UNKNOWN_MEMBER = "JSON member not found";
+ public static final String ERR_SSL_EXCEPTION = "SSL Exception";
+ public static final String ERR_INVALID_MAC_ADDRESS = "Invalid MAC address";
+ public static final String ERR_BRIDGE_OFFLINE = "Bridge is offline";
+ public static final String ERR_BRIDGE_NOT_DECLARED = "Bridge not found or not declared";
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.helpers;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * This class is used for handling errors.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtErrorHandler {
+ private String errorMessage = "";
+ private String infoMessage = "";
+
+ public AsuswrtErrorHandler() {
+ }
+
+ public AsuswrtErrorHandler(Exception ex) {
+ raiseError(ex);
+ }
+
+ /*
+ * Public functions
+ */
+
+ /**
+ * Raises a new error.
+ *
+ * @param exception the exception
+ */
+ public void raiseError(Exception ex) {
+ raiseError(ex, "");
+ }
+
+ /**
+ * Raises a new error.
+ *
+ * @param exception the exception
+ * @param infoMessage optional info message
+ */
+ public void raiseError(Exception ex, @Nullable String infoMessage) {
+ this.errorMessage = AsuswrtUtils.getValueOrDefault(ex.getMessage(), "");
+ this.infoMessage = AsuswrtUtils.getValueOrDefault(infoMessage, "");
+ }
+
+ /**
+ * Raises a new error.
+ *
+ * @param errorMessage the error message
+ * @param infoMessage optional info message
+ */
+ public void raiseError(String errorMessage, @Nullable String infoMessage) {
+ this.errorMessage = errorMessage;
+ this.infoMessage = AsuswrtUtils.getValueOrDefault(infoMessage, "");
+ }
+
+ /**
+ * Resets the error.
+ */
+ public void reset() {
+ errorMessage = "";
+ infoMessage = "";
+ }
+
+ /*
+ * Getters
+ */
+
+ /**
+ * Get the error message.
+ */
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ /**
+ * Get the info message.
+ */
+ public String getInfoMessage() {
+ return infoMessage;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.helpers;
+
+import java.util.regex.Pattern;
+
+import javax.measure.Unit;
+import javax.measure.quantity.Energy;
+import javax.measure.quantity.Power;
+import javax.measure.quantity.Time;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.HSBType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.types.State;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+/**
+ * The {@link AsuswrtUtils} contains utility helper functions.
+ *
+ * @author Christian Wild - Initial Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtUtils {
+ private static final Pattern PATTERN_MAC_PAIRS = Pattern.compile("^([a-fA-F0-9]{2}[:\\.-]?){5}[a-fA-F0-9]{2}$");
+ private static final Pattern PATTERN_MAC_TRIPLES = Pattern.compile("^([a-fA-F0-9]{3}[:\\.-]?){3}[a-fA-F0-9]{3}$");
+
+ /*
+ * Calculation utility methods
+ */
+
+ /**
+ * Limits a value between limits.
+ *
+ * @param value the value that should be limited
+ * @param lowerLimit will be returned if value is below
+ * @param upperLimit will be returned if value is higher
+ */
+ public static int limitVal(@Nullable Integer value, int lowerLimit, int upperLimit) {
+ if (value == null || value < lowerLimit) {
+ return lowerLimit;
+ } else if (value > upperLimit) {
+ return upperLimit;
+ }
+ return value;
+ }
+
+ /*
+ * Formatting utility methods
+ */
+
+ /**
+ * Returns a value or default value when the value is <code>null</code>.
+ *
+ * @param <T> Type of value
+ * @param value the value which should be checked
+ * @param defaultValue the default value that will be returned when <code>value</code> is <code>null</code>
+ */
+ public static <T> T getValueOrDefault(@Nullable T value, T defaultValue) {
+ return value == null ? defaultValue : value;
+ }
+
+ /**
+ * Formats a MAC address by replacing old separator characters and adding new ones.
+ *
+ * @param mac unformatted MAC address
+ * @param newSeparatorChar new separator characters (e.g. ":","-" )
+ */
+ public static String formatMac(String mac, char newSeparatorChar) {
+ String unformatedMac = unformatMac(mac);
+ String formatedMac = "";
+ try {
+ formatedMac = unformatedMac.replaceAll("(.{2})", "$1" + newSeparatorChar).substring(0, 17);
+ return formatedMac;
+ } catch (Exception e) {
+ return mac;
+ }
+ }
+
+ /**
+ * Unformats a MAC address. Removes all separator characters.
+ */
+ public static String unformatMac(String rawMac) {
+ String mac = rawMac;
+ mac = mac.replace("-", "");
+ mac = mac.replace(":", "");
+ mac = mac.replace(".", "");
+ mac = mac.replace(" ", "");
+ return mac;
+ }
+
+ /**
+ * Checks if a MAC address is valid.
+ */
+ public static boolean isValidMacAddress(String mac) {
+ // MAC-Addresses usually are 6 * 2 hex nibbles separated by colons,
+ // but apparently it is legal to have 4 * 3 hex nibbles as well,
+ // and the separators can be any of : or - or . or nothing.
+ return (PATTERN_MAC_PAIRS.matcher(mac).find() || PATTERN_MAC_TRIPLES.matcher(mac).find());
+ }
+
+ /**
+ * Converts a hexadecimal String to a byte array.
+ */
+ public static byte[] hexStringToByteArray(String s) {
+ int len = s.length();
+ byte[] data = new byte[len / 2];
+ try {
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
+ }
+ } catch (Exception e) {
+ }
+ return data;
+ }
+
+ /**
+ * Converts a {@link String} to a <code>boolean</code>.
+ *
+ * @param s the string to be converted ('0', '1', '-1', 'true', 'false')
+ * @param defVal default value if no match was found
+ */
+ public static boolean stringToBool(@Nullable String s, boolean defVal) {
+ if (s == null) {
+ return defVal;
+ } else if ("1".equals(s) || "-1".equals(s)) {
+ return true;
+ } else if ("0".equals(s)) {
+ return false;
+ } else {
+ try {
+ return Boolean.parseBoolean(s);
+ } catch (Exception e) {
+ return defVal;
+ }
+ }
+ }
+
+ /**
+ * Converts a {@link String} to an <code>int</code>.
+ *
+ * @param s the string to be converted
+ * @param defVal the default value if the string is not a number
+ */
+ public static int stringToInteger(@Nullable String s, int defVal) {
+ if (s == null) {
+ return defVal;
+ }
+ try {
+ return Integer.parseInt(s);
+ } catch (Exception e) {
+ return defVal;
+ }
+ }
+
+ /**
+ * Returns the provided string if it is not <code>null</code>, empty or blank. Otherwise the default value is
+ * returned.
+ *
+ * @param s the string to check
+ * @param defVal the default value
+ * @return the string or the default value
+ */
+ public static String stringOrDefault(@Nullable String s, String defVal) {
+ if (s == null || s.isEmpty() || s.isBlank()) {
+ return defVal;
+ }
+ return s;
+ }
+
+ /*
+ * JSON formatting
+ */
+
+ /**
+ * Checks if a String is valid JSON.
+ */
+ public static boolean isValidJson(String json) {
+ try {
+ Gson gson = new Gson();
+ JsonObject jsnObject = gson.fromJson(json, JsonObject.class);
+ return jsnObject != null;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Gets a {@link String} value from a {@link JsonObject}.
+ *
+ * @param jsonObject the object that will be searched for the key
+ * @param name the name of the key containing the value
+ * @param defVal the default value if the key does not exist
+ */
+ public static String jsonObjectToString(@Nullable JsonObject jsonObject, String name, String defVal) {
+ if (jsonObject != null && jsonObject.has(name)) {
+ return jsonObject.get(name).getAsString();
+ } else {
+ return defVal;
+ }
+ }
+
+ /**
+ * Gets a {@link String} value from a {@link JsonObject} using an empty String as default value.
+ *
+ * @param jsonObject the object that will be searched for the key
+ * @param name the name of the key containing the value
+ */
+ public static String jsonObjectToString(@Nullable JsonObject jsonObject, String name) {
+ return jsonObjectToString(jsonObject, name, "");
+ }
+
+ /**
+ * Gets a <code>boolean</code> value from a {@link JsonObject}.
+ *
+ * @param jsonObject the object that will be searched for the key
+ * @param name the name of the key containing the value
+ * @param defVal the default value if the key does not exist
+ */
+ public static boolean jsonObjectToBool(@Nullable JsonObject jsonObject, String name, boolean defVal) {
+ if (jsonObject != null && jsonObject.has(name)) {
+ JsonPrimitive o = jsonObject.getAsJsonPrimitive(name);
+ if (o.isBoolean()) {
+ return jsonObject.get(name).getAsBoolean();
+ } else if (o.isNumber()) {
+ Integer iVal = jsonObject.get(name).getAsInt();
+ return (iVal.equals(1) || iVal.equals(-1));
+ } else if (o.isString()) {
+ String val = jsonObject.get(name).getAsString();
+ return stringToBool(val, defVal);
+ }
+ }
+ return defVal;
+ }
+
+ /**
+ * Gets a <code>boolean</code> value from a {@link JsonObject} using <code>false</code> as default value.
+ *
+ * @param jsonObject the object that will be searched for the key
+ * @param name the name of the key containing the value
+ */
+ public static boolean jsonObjectToBool(@Nullable JsonObject jsonObject, String name) {
+ return jsonObjectToBool(jsonObject, name, false);
+ }
+
+ /**
+ * Gets an <code>int</code> value from a {@link JsonObject}.
+ *
+ * @param jsonObject the object that will be searched for the key
+ * @param name the name of the key containing the value
+ * @param defVal the default value if the key does not exist
+ */
+ public static int jsonObjectToInt(@Nullable JsonObject jsonObject, String name, int defVal) {
+ if (jsonObject != null && jsonObject.has(name)) {
+ JsonPrimitive o = jsonObject.getAsJsonPrimitive(name);
+ if (o.isNumber()) {
+ return jsonObject.get(name).getAsInt();
+ } else if (o.isString()) {
+ String val = jsonObject.get(name).getAsString();
+ return stringToInteger(val, defVal);
+ }
+ }
+ return defVal;
+ }
+
+ /**
+ * Gets an <code>int</code> value from a {@link JsonObject} using <code>0</code> as default value.
+ *
+ * @param jsonObject the object that will be searched for the key
+ * @param name the name of the key containing the value
+ */
+ public static int jsonObjectToInt(@Nullable JsonObject jsonObject, String name) {
+ return jsonObjectToInt(jsonObject, name, 0);
+ }
+
+ /**
+ * Gets a {@link Number} value from a {@link JsonObject}.
+ *
+ * @param jsonObject the object that will be searched for the key
+ * @param name the name of the key containing the value
+ * @param defVal the default value if the key does not exist
+ */
+ public static Number jsonObjectToNumber(@Nullable JsonObject jsonObject, String name, Number defVal) {
+ if (jsonObject != null && jsonObject.has(name)) {
+ return jsonObject.get(name).getAsNumber();
+ } else {
+ return defVal;
+ }
+ }
+
+ /**
+ * Gets a {@link Number} value from a {@link JsonObject} using <code>0</code> as default value.
+ *
+ * @param jsonObject the object that will be searched for the key
+ * @param name the name of the key containing the value
+ */
+ public static Number jsonObjectToNumber(@Nullable JsonObject jsonObject, String name) {
+ return jsonObjectToNumber(jsonObject, name, 0);
+ }
+
+ /*
+ * Type utility methods
+ */
+
+ /**
+ * Returns an {@link OnOffType} from a {@link Boolean}.
+ */
+ public static OnOffType getOnOffType(@Nullable Boolean boolVal) {
+ return (boolVal != null ? boolVal ? OnOffType.ON : OnOffType.OFF : OnOffType.OFF);
+ }
+
+ /**
+ * Returns an {@link OnOffType} from an {@link Integer}.
+ */
+ public static OnOffType getOnOffType(Integer intVal) {
+ return intVal == 0 ? OnOffType.OFF : OnOffType.ON;
+ }
+
+ /**
+ * Returns a {@link StringType} from a {@link String}.
+ */
+ public static StringType getStringType(@Nullable String strVal) {
+ return new StringType(strVal != null ? strVal : "");
+ }
+
+ /**
+ * Returns a {@link DecimalType} from a {@link Double}.
+ */
+ public static DecimalType getDecimalType(@Nullable Double numVal) {
+ return new DecimalType((numVal != null ? numVal : 0));
+ }
+
+ /**
+ * Returns a {@link DecimalType} from an {@link Integer}.
+ */
+ public static DecimalType getDecimalType(@Nullable Integer numVal) {
+ return new DecimalType((numVal != null ? numVal : 0));
+ }
+
+ /**
+ * Returns a {@link DecimalType} from a {@link Long}.
+ */
+ public static DecimalType getDecimalType(@Nullable Long numVal) {
+ return new DecimalType((numVal != null ? numVal : 0));
+ }
+
+ /**
+ * Returns a {@link PercentType} from an {@link Integer}.
+ */
+ public static PercentType getPercentType(@Nullable Integer numVal) {
+ Integer val = limitVal(numVal, 0, 100);
+ return new PercentType(val);
+ }
+
+ /**
+ * Returns a {@link HSBType} from {@link Integer}s.
+ *
+ * @param hue the hue color
+ * @param saturation the saturation (0-100)
+ * @param brightness the brightness (0-100)
+ */
+ public static HSBType getHSBType(Integer hue, Integer saturation, Integer brightness) {
+ DecimalType h = new DecimalType(hue);
+ PercentType s = new PercentType(saturation);
+ PercentType b = new PercentType(brightness);
+ return new HSBType(h, s, b);
+ }
+
+ /**
+ * Returns a {@link QuantityType} with the {@link Time} unit.
+ */
+ public static QuantityType<Time> getTimeType(@Nullable Number numVal, Unit<Time> unit) {
+ return new QuantityType<>((numVal != null ? numVal : 0), unit);
+ }
+
+ /**
+ * Returns a {@link QuantityType} with the {@link Power} unit.
+ */
+ public static QuantityType<Power> getPowerType(@Nullable Number numVal, Unit<Power> unit) {
+ return new QuantityType<>((numVal != null ? numVal : 0), unit);
+ }
+
+ /**
+ * Returns a {@link QuantityType} with the {@link Energy} unit.
+ */
+ public static QuantityType<Energy> getEnergyType(@Nullable Number numVal, Unit<Energy> unit) {
+ return new QuantityType<>((numVal != null ? numVal : 0), unit);
+ }
+
+ /**
+ * Returns a {@link QuantityType} with the provided unit.
+ */
+ public static State getQuantityType(@Nullable Number numVal, Unit<?> unit) {
+ return new QuantityType<>((numVal != null ? numVal : 0), unit);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.INTERFACE_CLIENT;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtClientInfo} class stores client data.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtClientInfo {
+ private AsuswrtTraffic traffic = new AsuswrtTraffic();
+ private Integer defaultType = 0;
+ private String dpiDevice = "";
+ private String dpiType = "";
+ private String from = "";
+ private String group = "";
+ private String internetMode = "";
+ private Boolean internetState = false;
+ private String ip = "";
+ private String ipMethod = "";
+ private Boolean isGateway = false;
+ private Boolean isGN = false;
+ private Boolean isITunes = false;
+ private Boolean isLogin = false;
+ private Boolean isOnline = false;
+ private Boolean isPrinter = false;
+ private Boolean isWebServer = false;
+ private Integer isWL = 0;
+ private String keeparp = "";
+ private String mac = "";
+ private Boolean macRepeat = false;
+ private String name = "";
+ private String nickName = "";
+ private Integer opMode = 0;
+ private String qosLevel = "";
+ private Integer rog = 0;
+ private Integer rssi = 0;
+ private String ssid = "";
+ private String vendor = "";
+ private String wlConnectTime = "";
+ private Integer wtfast = 0;
+
+ public AsuswrtClientInfo() {
+ }
+
+ public AsuswrtClientInfo(JsonObject jsonObject) {
+ traffic = new AsuswrtTraffic(INTERFACE_CLIENT);
+ setData(jsonObject);
+ }
+
+ public void setData(JsonObject jsonObject) {
+ traffic.setData(jsonObject);
+ defaultType = jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_DEFTYPE, defaultType);
+ dpiDevice = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_DPIDEVICE, dpiDevice);
+ dpiType = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_DPITYPE, dpiType);
+ from = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_IPFROM, from);
+ group = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_GROUP, group);
+ internetMode = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_INETMODE, internetMode);
+ internetState = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_INETSTATE, internetState);
+ ip = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_IP, ip);
+ ipMethod = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_IPMETHOD, ipMethod);
+ isGateway = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_IPGATEWAY, isGateway);
+ isGN = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_GN, isGN);
+ isITunes = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_ITUNES, isITunes);
+ isLogin = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_LOGIN, isLogin);
+ isOnline = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_ONLINE, isOnline);
+ isPrinter = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_PRINTER, isPrinter);
+ isWebServer = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_WEBSRV, isWebServer);
+ isWL = jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_WIFI, isWL);
+ keeparp = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_KEEPARP, keeparp);
+ mac = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_MAC, mac);
+ macRepeat = jsonObjectToBool(jsonObject, JSON_MEMBER_CLIENT_MACREPEAT, macRepeat);
+ name = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_NAME, name);
+ nickName = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_NICK, nickName);
+ opMode = jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_MODE, opMode);
+ qosLevel = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_QOSLVL, qosLevel);
+ rog = jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_ROG, rog);
+ rssi = jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_RSSI, rssi);
+ ssid = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_SSID, ssid);
+ vendor = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_VENDOR, vendor);
+ wlConnectTime = jsonObjectToString(jsonObject, JSON_MEMBER_CLIENT_CONNECTTIME, wlConnectTime);
+ wtfast = jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_WTFAST, wtfast);
+ }
+
+ /*
+ * Getters
+ */
+
+ public AsuswrtTraffic getTraffic() {
+ return traffic;
+ }
+
+ public Integer getDefaultType() {
+ return defaultType;
+ }
+
+ public String getDpiDevice() {
+ return dpiDevice;
+ }
+
+ public String getDpiType() {
+ return dpiType;
+ }
+
+ public String getIpFrom() {
+ return from;
+ }
+
+ public String getGroup() {
+ return group;
+ }
+
+ public String getInternetMode() {
+ return internetMode;
+ }
+
+ public Boolean getInternetState() {
+ return internetState;
+ }
+
+ public String getIP() {
+ return ip;
+ }
+
+ public String getIpMethod() {
+ return ipMethod;
+ }
+
+ public Boolean isGateway() {
+ return isGateway;
+ }
+
+ public Boolean isGN() {
+ return isGN;
+ }
+
+ public Boolean isITunes() {
+ return isITunes;
+ }
+
+ public Boolean isLogin() {
+ return isLogin;
+ }
+
+ public Boolean isOnline() {
+ return isOnline;
+ }
+
+ public Boolean isPrinter() {
+ return isPrinter;
+ }
+
+ public Boolean isWebServer() {
+ return isWebServer;
+ }
+
+ public Integer isWL() {
+ return isWL;
+ }
+
+ public Boolean isWiFiConnected() {
+ return isWL > 0;
+ }
+
+ public String getKeepArp() {
+ return keeparp;
+ }
+
+ public String getMac() {
+ return mac;
+ }
+
+ public Boolean getMacRepeat() {
+ return macRepeat;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getNickName() {
+ return nickName;
+ }
+
+ public Integer getOpMode() {
+ return opMode;
+ }
+
+ public String getQosLevel() {
+ return qosLevel;
+ }
+
+ public Integer getROG() {
+ return rog;
+ }
+
+ public Integer getRSSI() {
+ return rssi;
+ }
+
+ public String getSSID() {
+ return ssid;
+ }
+
+ public String getVendor() {
+ return vendor;
+ }
+
+ public String getWlanConnectTime() {
+ return wlConnectTime;
+ }
+
+ public Integer getWtFast() {
+ return wtfast;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtErrorConstants.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.isValidMacAddress;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtClientList} class stores a list of {@link AsuswrtClientInfo}.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtClientList implements Iterable<AsuswrtClientInfo> {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtClientList.class);
+ private List<AsuswrtClientInfo> clientList = new ArrayList<>();
+
+ public AsuswrtClientList() {
+ }
+
+ public AsuswrtClientList(JsonObject jsonObject) {
+ setData(jsonObject);
+ }
+
+ @Override
+ public Iterator<AsuswrtClientInfo> iterator() {
+ return clientList.iterator();
+ }
+
+ /**
+ * Sets the {@link AsuswrtClientList} using a {@link JsonObject}.
+ */
+ public void setData(JsonObject jsonObject) {
+ clientList.clear();
+ try {
+ JsonObject jsonList = jsonObject.getAsJsonObject(JSON_MEMBER_CLIENTS);
+ // Remove the member MAC list, it contains only online clients
+ jsonList.remove(JSON_MEMBER_MACLIST);
+ jsonList.remove(JSON_MEMBER_API_LEVEL);
+ // Iterate over the MAC addresses
+ jsonList.keySet().forEach(macAddress -> {
+ if (isValidMacAddress(macAddress)) {
+ AsuswrtClientInfo clientInfo = new AsuswrtClientInfo(jsonList.getAsJsonObject(macAddress));
+ addClient(clientInfo);
+ } else {
+ logger.trace("getClientlist: {} '{}'", ERR_INVALID_MAC_ADDRESS, macAddress);
+ }
+ });
+ } catch (Exception e) {
+ logger.debug("getClientlist: {} - {}'", ERR_JSON_FORMAT, e.getMessage());
+ }
+ }
+
+ /**
+ * Adds {@link AsuswrtClientInfo} to the list.
+ */
+ private void addClient(AsuswrtClientInfo clientInfo) {
+ clientList.add(clientInfo);
+ }
+
+ /*
+ * Getters
+ */
+
+ /**
+ * Gets {@link AsuswrtClientInfo} from the list for a client based on its name.
+ *
+ * @param clientName the name of the client for which the info is returned
+ */
+ public AsuswrtClientInfo getClientByName(String clientName) {
+ for (AsuswrtClientInfo client : this.clientList) {
+ if (client.getName().equals(clientName)) {
+ return client;
+ }
+ }
+ return new AsuswrtClientInfo();
+ }
+
+ /**
+ * Gets {@link AsuswrtClientInfo} from the list for a client based on its MAC address.
+ *
+ * @param clientMAC the MAC address of the client for which the info is returned
+ */
+ public AsuswrtClientInfo getClientByMAC(String clientMAC) {
+ for (AsuswrtClientInfo client : this.clientList) {
+ if (client.getMac().equals(clientMAC)) {
+ return client;
+ }
+ }
+ return new AsuswrtClientInfo();
+ }
+
+ /**
+ * Gets {@link AsuswrtClientInfo} from the list for a client based on its IP address.
+ *
+ * @param clientIP the IP address of the client for which the info is returned
+ */
+ public AsuswrtClientInfo getClientByIP(String clientIP) {
+ for (AsuswrtClientInfo client : this.clientList) {
+ if (client.getIP().equals(clientIP)) {
+ return client;
+ }
+ }
+ return new AsuswrtClientInfo();
+ }
+
+ /*
+ * Returns a <code>;</code> separated list with client names and MAC addresses.
+ */
+ public String getClientList() {
+ StringBuilder clients = new StringBuilder();
+ for (AsuswrtClientInfo client : this.clientList) {
+ clients.append(client.getName() + " [" + client.getMac() + "]; ");
+ }
+ return clients.toString();
+ }
+
+ /*
+ * Returns a <code>;</code> separated list with client names.
+ */
+ public String getClientNames() {
+ return clientList.stream().map(AsuswrtClientInfo::getName).collect(Collectors.joining("; "));
+ }
+
+ /**
+ * Returns the number of clients in the list.
+ */
+ public Integer getCount() {
+ return clientList.size();
+ }
+
+ /*
+ * Returns a <code>;</code> separated list with MAC addresses.
+ */
+ public String getMacAddresses() {
+ StringBuilder clients = new StringBuilder();
+ for (AsuswrtClientInfo client : this.clientList) {
+ clients.append(client.getMac() + "; ");
+ }
+ return clients.toString();
+ }
+
+ /**
+ * Returns a {@link AsuswrtClientList} of online clients.
+ */
+ public AsuswrtClientList getOnlineClients() {
+ AsuswrtClientList clients = new AsuswrtClientList();
+ for (AsuswrtClientInfo client : this.clientList) {
+ if (client.isOnline()) {
+ clients.addClient(client);
+ }
+ }
+ return clients;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link AsuswrtConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtConfiguration {
+
+ // Thing configuration properties
+ public static final String CONFIG_USER = "username";
+ public static final String CONFIG_PASS = "password";
+ public static final String CONFIG_HOSTNAME = "hostname";
+ public static final String CONFIG_UPDATE_INTERVAL = "refreshInterval";
+ public static final String CONFIG_SSL_AUTH = "useSSL";
+ public static final String CONFIG_PORT_HTTP = "httpPort";
+ public static final String CONFIG_PORT_HTTPS = "httpsPort";
+
+ // Thing configuration parameters
+ public String hostname = "";
+ public String username = "";
+ public String password = "";
+ public int pollingInterval = 20;
+ public int reconnectInterval = 60;
+ public int discoveryInterval = 3600;
+ public int httpPort = 80;
+ public int httpsPort = 443;
+ public boolean autoDiscoveryEnabled = false;
+ public boolean useSSL = false;
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import java.util.Base64;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This class is used for storing Asuswrt credentials.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtCredentials {
+ private String username = "";
+ private String password = "";
+ private String encodedCredentials = "";
+
+ public AsuswrtCredentials() {
+ }
+
+ public AsuswrtCredentials(AsuswrtConfiguration routerConfig) {
+ setCredentials(routerConfig.username, routerConfig.password);
+ }
+
+ public AsuswrtCredentials(String username, String password) {
+ setCredentials(username, password);
+ }
+
+ /*
+ * Private methods
+ */
+
+ /**
+ * Stores the given credentials.
+ */
+ private void setCredentials(String username, String password) {
+ this.username = username;
+ this.password = password;
+ encodedCredentials = b64encode(username + ":" + password);
+ }
+
+ /**
+ * Encodes a String using Base64.
+ */
+ private String b64encode(String string) {
+ return Base64.getEncoder().encodeToString((string).getBytes());
+ }
+
+ /*
+ * Public methods
+ */
+
+ /**
+ * Returns Base64 encoded credentials.
+ *
+ * @return 'username:password' as Base64 encoded string
+ */
+ public String getEncodedCredentials() {
+ return encodedCredentials;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtInterfaceList} class stores a list of {@link AsuswrtIpInfo}.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtInterfaceList implements Iterable<AsuswrtIpInfo> {
+ private List<AsuswrtIpInfo> ipInfoList = new ArrayList<>();
+
+ public AsuswrtInterfaceList() {
+ }
+
+ @Override
+ public Iterator<AsuswrtIpInfo> iterator() {
+ return ipInfoList.iterator();
+ }
+
+ /*
+ * Setters
+ */
+
+ /**
+ * Adds an {@link AsuswrtIpInfo} to the list.
+ */
+ private void addInterface(AsuswrtIpInfo ipInfo) {
+ ipInfoList.add(ipInfo);
+ }
+
+ /**
+ * Sets the {@link AsuswrtInterfaceList} using a {@link JsonObject}.
+ */
+ public void setData(String ifName, JsonObject jsonObject) {
+ if (hasInterface(ifName)) {
+ getByName(ifName).setData(jsonObject);
+ } else {
+ addInterface(new AsuswrtIpInfo(ifName, jsonObject));
+ }
+ }
+
+ /*
+ * Getters
+ */
+
+ /**
+ * Gets {@link AsuswrtIpInfo} from the list for an interface based on its name.
+ *
+ * @param ifName the name of the interface for which the info is returned
+ */
+ public AsuswrtIpInfo getByName(String ifName) {
+ for (AsuswrtIpInfo ipInfo : ipInfoList) {
+ if (ipInfo.getName().equals(ifName)) {
+ return ipInfo;
+ }
+ }
+ return new AsuswrtIpInfo();
+ }
+
+ /**
+ * Gets {@link AsuswrtIpInfo} from the list for an interface based on its MAC address.
+ *
+ * @param ipInfoMAC the MAC address of the interface for which the info is returned
+ */
+ public AsuswrtIpInfo getByMAC(String ipInfoMAC) {
+ for (AsuswrtIpInfo ipInfo : ipInfoList) {
+ if (ipInfo.getMAC().equals(ipInfoMAC)) {
+ return ipInfo;
+ }
+ }
+ return new AsuswrtIpInfo();
+ }
+
+ /**
+ * Gets {@link AsuswrtIpInfo} from the list for an interface based on its IP address.
+ *
+ * @param ipAddress the IP address of the interface for which the info is returned
+ */
+ public AsuswrtIpInfo getByIP(String ipAddress) {
+ for (AsuswrtIpInfo ipInfo : ipInfoList) {
+ if (ipInfo.getIpAddress().equals(ipAddress)) {
+ return ipInfo;
+ }
+ }
+ return new AsuswrtIpInfo();
+ }
+
+ /**
+ * Checks if an interface with the given name is in the list.
+ *
+ * @param ifName the name of the interface
+ */
+ public boolean hasInterface(String ifName) {
+ for (AsuswrtIpInfo ipInfo : ipInfoList) {
+ if (ipInfo.getName().equals(ifName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtIpInfo} class stores IP data.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtIpInfo {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtIpInfo.class);
+ private AsuswrtTraffic traffic = new AsuswrtTraffic();
+ private String ifName = "";
+ private String hwAddress = "";
+ private String ipAddress = "";
+ private String ipProto = "";
+ private String subnet = "";
+ private String gateway = "";
+ private String dnsServer = "";
+ private Boolean connected = false;
+
+ public AsuswrtIpInfo() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param interfaceName name of interface
+ * @param jsonObject with ipInfo
+ */
+ public AsuswrtIpInfo(String ifName, JsonObject jsonObject) {
+ this.ifName = ifName;
+ traffic = new AsuswrtTraffic(ifName);
+ setData(jsonObject);
+ }
+
+ /*
+ * Setters
+ */
+
+ public void setData(JsonObject jsonObject) {
+ if (ifName.startsWith(INTERFACE_LAN)) {
+ logger.trace("(AsuswrtIpInfo) setData for interface {}", INTERFACE_LAN);
+ hwAddress = jsonObjectToString(jsonObject, JSON_MEMBER_MAC, hwAddress);
+ ipAddress = jsonObjectToString(jsonObject, JSON_MEMBER_LAN_IP, ipAddress);
+ subnet = jsonObjectToString(jsonObject, JSON_MEMBER_LAN_NETMASK, subnet);
+ gateway = jsonObjectToString(jsonObject, JSON_MEMBER_LAN_GATEWAY, gateway);
+ ipProto = jsonObjectToString(jsonObject, JSON_MEMBER_LAN_PROTO, ipProto);
+ } else if (ifName.startsWith(INTERFACE_WAN)) {
+ logger.trace("(AsuswrtIpInfo) setData for interface {}", INTERFACE_WAN);
+ hwAddress = jsonObjectToString(jsonObject, JSON_MEMBER_MAC, hwAddress);
+ ipAddress = jsonObjectToString(jsonObject, JSON_MEMBER_WAN_IP, ipAddress);
+ subnet = jsonObjectToString(jsonObject, JSON_MEMBER_WAN_NETMASK, subnet);
+ gateway = jsonObjectToString(jsonObject, JSON_MEMBER_WAN_GATEWAY, gateway);
+ ipProto = jsonObjectToString(jsonObject, JSON_MEMBER_WAN_PROTO, ipProto);
+ dnsServer = jsonObjectToString(jsonObject, JSON_MEMBER_WAN_DNS_SERVER, dnsServer);
+ connected = (jsonObjectToInt(jsonObject, JSON_MEMBER_WAN_CONNECTED) == 1);
+ }
+ if (jsonObject.has(JSON_MEMBER_TRAFFIC)) {
+ traffic.setData(jsonObject.getAsJsonObject(JSON_MEMBER_TRAFFIC));
+ }
+ }
+
+ /*
+ * Getters
+ */
+
+ public AsuswrtTraffic getTraffic() {
+ return traffic;
+ }
+
+ public String getMAC() {
+ return hwAddress;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public String getSubnet() {
+ return subnet;
+ }
+
+ public String getGateway() {
+ return gateway;
+ }
+
+ public String getIpProto() {
+ return ipProto;
+ }
+
+ public String getDNSNServer() {
+ return dnsServer;
+ }
+
+ public String getName() {
+ return ifName;
+ }
+
+ public Boolean isConnected() {
+ return connected;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtRouterInfo} class stores the router data
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtRouterInfo {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtRouterInfo.class);
+ private String productId = "";
+ private String fwVersion = "";
+ private String fwBuild = "";
+ private String macAddress = "";
+ private Map<String, AsuswrtUsage> usageStats = new HashMap<>();
+
+ public AsuswrtRouterInfo() {
+ }
+
+ public AsuswrtRouterInfo(JsonObject jsonObject) {
+ setSysInfo(jsonObject);
+ }
+
+ /*
+ * Setters
+ */
+
+ public void setAllData(JsonObject jsonObject) {
+ setSysInfo(jsonObject);
+ setUsageStats(jsonObject);
+ }
+
+ public void setSysInfo(JsonObject jsonObject) {
+ try {
+ productId = jsonObject.get(JSON_MEMBER_PRODUCTID).toString();
+ fwVersion = jsonObject.get(JSON_MEMBER_FIRMWARE).toString();
+ fwBuild = jsonObject.get(JSON_MEMBER_BUILD).toString();
+ macAddress = jsonObject.get(JSON_MEMBER_MAC).toString();
+ } catch (Exception e) {
+ logger.trace("incomplete SysInfo");
+ }
+ }
+
+ public void setUsageStats(JsonObject jsonObject) {
+ JsonObject jsnMemUsage = jsonObject.getAsJsonObject(JSON_MEMBER_MEM_USAGE);
+ JsonObject jsnCpuUsage = jsonObject.getAsJsonObject(JSON_MEMBER_CPU_USAGE);
+ // Get memory usage
+ if (jsnMemUsage != null) {
+ usageStats.put(JSON_MEMBER_MEM_USAGE,
+ new AsuswrtUsage(jsnMemUsage, JSON_MEMBER_MEM_TOTAL, JSON_MEMBER_MEM_USED));
+ }
+ // Loop cpu usages
+ if (jsnCpuUsage != null) {
+ for (Integer i = 1; i <= USAGE_CPU_COUNT; i++) {
+ String member = JSON_MEMBER_CPU_USAGE + "_" + i;
+ String total = JSON_MEMBER_CPU_TOTAL.replace("{x}", "" + i);
+ String used = JSON_MEMBER_CPU_USED.replace("{x}", "" + i);
+ if (jsnCpuUsage.has(total) && jsnCpuUsage.has(used)) {
+ usageStats.put(member, new AsuswrtUsage(jsnCpuUsage, total, used));
+ }
+ }
+ }
+ }
+
+ /*
+ * Getters
+ */
+
+ public String getProductId() {
+ return productId;
+ }
+
+ public String getFirmwareVersion() {
+ return fwVersion + " (" + fwBuild + ")";
+ }
+
+ public String getMAC() {
+ return macAddress;
+ }
+
+ public AsuswrtUsage getMemUsage() {
+ if (usageStats.containsKey(JSON_MEMBER_MEM_USAGE)) {
+ AsuswrtUsage usage = usageStats.get(JSON_MEMBER_MEM_USAGE);
+ if (usage != null) {
+ return usage;
+ }
+ }
+ return new AsuswrtUsage();
+ }
+
+ /**
+ * Gets the CPU usage for a core.
+ *
+ * @param coreNum the core number
+ * @return the {@link AsuswrtUsage} for the given core
+ */
+ public AsuswrtUsage getCpuUsage(Integer coreNum) {
+ String coreKey = JSON_MEMBER_CPU_USAGE + "_" + coreNum;
+ if (usageStats.containsKey(coreKey)) {
+ AsuswrtUsage usage = usageStats.get(coreKey);
+ if (usage != null) {
+ return usage;
+ }
+ }
+ return new AsuswrtUsage();
+ }
+
+ /**
+ * Get CPU usage average over all cores.
+ *
+ * @return the {@link AsuswrtUsage} with CPU usage average over all cores
+ */
+ public AsuswrtUsage getCpuAverage() {
+ String coreKey;
+ AsuswrtUsage coreStatsX;
+ Integer total = 0, used = 0, coreNum;
+ for (coreNum = 1; coreNum <= USAGE_CPU_COUNT; coreNum++) {
+ coreKey = JSON_MEMBER_CPU_USAGE + "_" + coreNum;
+ coreStatsX = usageStats.get(coreKey);
+ if (coreStatsX != null) {
+ total += coreStatsX.getTotal();
+ used += coreStatsX.getUsed();
+ }
+ }
+ if (coreNum > 1) {
+ total = total / coreNum - 1;
+ used = used / coreNum - 1;
+ }
+ return new AsuswrtUsage(total, used);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.*;
+
+import java.time.LocalDate;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtTraffic} class handles traffic statistics
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtTraffic {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtTraffic.class);
+ private Double curTX = 0.0;
+ private Double curRX = 0.0;
+ private Integer totalTX = 0;
+ private Integer totalRX = 0;
+ private Integer zeroHourTX = 0;
+ private Integer zeroHourRX = 0;
+ private LocalDate zeroHourDate = LocalDate.now();
+ private Long lastUpdate = 0L;
+ private String representationProperty = "";
+
+ public AsuswrtTraffic() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param representationProperty representationProperty of the device (i.e. interfaceName)
+ */
+ public AsuswrtTraffic(String representationProperty) {
+ this.representationProperty = representationProperty.toLowerCase();
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param jsonObject stores the data
+ * @param representationProperty representationProperty of the device (i.e. interfaceName)
+ */
+ public AsuswrtTraffic(JsonObject jsonObject, String representationProperty) {
+ this.representationProperty = representationProperty;
+ setData(jsonObject);
+ }
+
+ public void setData(JsonObject jsonObject) {
+ Integer intRX;
+ Integer intTX;
+ if (representationProperty.startsWith(INTERFACE_LAN)) {
+ intRX = getTrafficFromHex(jsonObject, JSON_MEMBER_LAN_RX);
+ intTX = getTrafficFromHex(jsonObject, JSON_MEMBER_LAN_TX);
+ curRX = calculateCurrentTraffic(intRX, totalRX);
+ curTX = calculateCurrentTraffic(intTX, totalTX);
+ totalRX = getTrafficFromHex(jsonObject, JSON_MEMBER_LAN_RX);
+ totalTX = getTrafficFromHex(jsonObject, JSON_MEMBER_LAN_TX);
+ } else if (representationProperty.startsWith(INTERFACE_WAN)) {
+ intRX = getTrafficFromHex(jsonObject, JSON_MEMBER_INET_RX);
+ intTX = getTrafficFromHex(jsonObject, JSON_MEMBER_INET_TX);
+ curRX = calculateCurrentTraffic(intRX, totalRX);
+ curTX = calculateCurrentTraffic(intTX, totalTX);
+ totalRX = getTrafficFromHex(jsonObject, JSON_MEMBER_INET_RX);
+ totalTX = getTrafficFromHex(jsonObject, JSON_MEMBER_INET_TX);
+ } else if (representationProperty.startsWith(INTERFACE_WLAN)) {
+ for (int i = 0; i < 1; i++) {
+ intRX = getTrafficFromHex(jsonObject, JSON_MEMBER_WLAN_RX.replace("{}", Integer.toString(i)));
+ intTX = getTrafficFromHex(jsonObject, JSON_MEMBER_WLAN_TX.replace("{}", Integer.toString(i)));
+ curRX = calculateCurrentTraffic(intRX, totalRX);
+ curTX = calculateCurrentTraffic(intTX, totalTX);
+ totalRX = getTrafficFromHex(jsonObject, JSON_MEMBER_INET_RX);
+ totalTX = getTrafficFromHex(jsonObject, JSON_MEMBER_INET_TX);
+ }
+ } else if (INTERFACE_CLIENT.equals(representationProperty)) {
+ curRX = Double.valueOf(jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_RXCUR, -1));
+ curTX = Double.valueOf(jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_TXCUR, -1));
+ totalRX = jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_RXTOTAL, -1);
+ totalTX = jsonObjectToInt(jsonObject, JSON_MEMBER_CLIENT_TXTOTAL, -1);
+ } else {
+ logger.trace("({}) can't set Trafficdata", representationProperty);
+ }
+ lastUpdate = System.currentTimeMillis();
+ setZeroHourTraffic(totalRX, totalTX);
+ }
+
+ /**
+ * Saves the traffic values at the start of a new day.
+ */
+ private void setZeroHourTraffic(Integer totalRX, Integer totalTX) {
+ LocalDate today = LocalDate.now();
+ if (today.isAfter(zeroHourDate) || zeroHourRX > totalRX) {
+ zeroHourRX = totalRX;
+ zeroHourTX = totalTX;
+ zeroHourDate = today;
+ }
+ }
+
+ /**
+ * Gets the traffic as {@link Integer} value from a hexadecimal value in a {@link JsonObject}.
+ *
+ * @param jsonObject the object containing the values
+ * @param jsonMember the name of the key that stores the value
+ * @return the traffic value
+ */
+ private Integer getTrafficFromHex(JsonObject jsonObject, String jsonMember) {
+ Long lngVal;
+ if (jsonObject.has(jsonMember)) {
+ String hex = jsonObjectToString(jsonObject, jsonMember);
+ try {
+ lngVal = Long.decode(hex);
+ lngVal = lngVal * 8 / 1024 / 1024 / 2;
+ return lngVal.intValue();
+ } catch (Exception e) {
+ logger.debug("({}) error calculating traffic from hex '{}'", representationProperty, hex);
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Calculates the traffic from the actual and old total traffic using the time span.
+ *
+ * @param actVal the actual value
+ * @param oldVal the old value
+ * @return the current traffic value
+ */
+ private Double calculateCurrentTraffic(Integer actVal, Integer oldVal) {
+ if (lastUpdate > 0) {
+ Long timeSpan = (System.currentTimeMillis() - lastUpdate) / 1000;
+ Integer div = 0;
+ try {
+ if (actVal >= 0) {
+ div = actVal - oldVal;
+ return Double.valueOf(div / timeSpan.intValue());
+ }
+ } catch (Exception e) {
+ logger.debug("({}) error calculating traffic from timeSpan '{}/{}'", representationProperty, div,
+ timeSpan);
+ }
+ }
+ return -1.0;
+ }
+
+ /*
+ * Getters
+ */
+
+ public Double getCurrentRX() {
+ return curRX;
+ }
+
+ public Double getCurrentTX() {
+ return curTX;
+ }
+
+ public Integer getTotalRX() {
+ return totalRX;
+ }
+
+ public Integer getTotalTX() {
+ return totalTX;
+ }
+
+ public Integer getTodayRX() {
+ return totalRX - zeroHourRX;
+ }
+
+ public Integer getTodayTX() {
+ return totalTX - zeroHourTX;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.structures;
+
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.jsonObjectToInt;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtUsage} class handles usage statistics
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtUsage {
+ private Integer free = 0;
+ private Integer used = 0;
+ private Integer total = 0;
+
+ public AsuswrtUsage() {
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param jsonObject jsonObject data is stored
+ * @param totalKey name of key total available is stored
+ * @param usedKey name of key used is stored
+ */
+ public AsuswrtUsage(JsonObject jsonObject, String totalKey, String usedKey) {
+ setData(jsonObject, totalKey, usedKey);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param totalUsage the total usage
+ * @param used the usage
+ */
+ public AsuswrtUsage(Integer totalUsage, Integer used) {
+ setData(totalUsage, used);
+ }
+
+ /*
+ * Setters
+ */
+
+ /**
+ * Sets the usage data from a JSON object.
+ *
+ * @param jsonObject the JSON object containing the data
+ * @param totalKey the key name with the 'total available' value
+ * @param usedKey the key name with the 'used' value
+ */
+ public void setData(JsonObject jsonObject, String totalKey, String usedKey) {
+ total = jsonObjectToInt(jsonObject, totalKey, 0);
+ used = jsonObjectToInt(jsonObject, usedKey, 0);
+ free = total - used;
+ }
+
+ /**
+ * Sets usage data from integer values.
+ *
+ * @param totalUsage the total available value
+ * @param used the used value
+ */
+ public void setData(Integer totalUsage, Integer used) {
+ total = totalUsage;
+ this.used = used;
+ free = total - used;
+ }
+
+ /*
+ * Getters
+ */
+
+ public Integer getTotal() {
+ return total;
+ }
+
+ public Integer getUsed() {
+ return used;
+ }
+
+ public Integer getFree() {
+ return free;
+ }
+
+ public Integer getUsedPercent() {
+ if (total > 0) {
+ return ((used * 100) / total);
+ }
+ return 0;
+ }
+
+ public Integer getFreePercent() {
+ if (total > 0) {
+ return ((free * 100) / total);
+ }
+ return 0;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.things;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtClientInfo;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtTraffic;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AsuswrtClient} is used as {@link org.openhab.core.thing.binding.ThingHandler ThingHandler} for router
+ * clients.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtClient extends BaseThingHandler {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtClient.class);
+ private final AsuswrtRouter router;
+ private Map<String, Object> oldStates = new HashMap<>();
+ protected final String uid;
+
+ public AsuswrtClient(Thing thing, AsuswrtRouter router) {
+ super(thing);
+ this.router = router;
+ this.uid = getThing().getUID().getAsString();
+ }
+
+ @Override
+ public void initialize() {
+ logger.trace("({}) Initializing thing ", uid);
+ router.queryDeviceData(false);
+ refreshData();
+ updateStatus(ThingStatus.ONLINE);
+ }
+
+ /*
+ * Commands and events
+ */
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType) {
+ refreshData();
+ }
+ }
+
+ public void updateClientProperties(AsuswrtClientInfo clientInfo) {
+ logger.trace("({}) clientPropertiesChanged ", uid);
+ Map<String, String> properties = editProperties();
+ properties.put(Thing.PROPERTY_MAC_ADDRESS, clientInfo.getMac());
+ properties.put(Thing.PROPERTY_VENDOR, clientInfo.getVendor());
+ properties.put(PROPERTY_CLIENT_NAME, clientInfo.getName());
+ updateProperties(properties);
+ }
+
+ public void updateClientChannels(AsuswrtClientInfo clientInfo) {
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_STATE), getOnOffType(clientInfo.isOnline()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_INTERNET),
+ getOnOffType(clientInfo.getInternetState()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_IP), getStringType(clientInfo.getIP()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_METHOD),
+ getStringType(clientInfo.getIpMethod()));
+ }
+
+ private void updateTrafficChannels(AsuswrtTraffic traffic) {
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_CURRENT_RX),
+ getQuantityType(traffic.getCurrentRX(), Units.MEGABIT_PER_SECOND));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_CURRENT_TX),
+ getQuantityType(traffic.getCurrentTX(), Units.MEGABIT_PER_SECOND));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_TODAY_RX),
+ getQuantityType(traffic.getTodayRX(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_TODAY_TX),
+ getQuantityType(traffic.getTodayTX(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_TOTAL_RX),
+ getQuantityType(traffic.getTotalRX(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_TOTAL_TX),
+ getQuantityType(traffic.getTotalTX(), Units.MEGABYTE));
+ }
+
+ /**
+ * Fires events on {@link AsuswrtClientInfo} changes.
+ */
+ private void fireEvents(AsuswrtClientInfo clientInfo) {
+ if (checkForStateChange(CHANNEL_GROUP_NETWORK, clientInfo.isOnline())) {
+ if (clientInfo.isOnline()) {
+ triggerChannel(getChannelID(CHANNEL_GROUP_NETWORK, EVENT_CLIENT_CONNECTION), EVENT_STATE_CONNECTED);
+ router.fireEvent(getChannelID(CHANNEL_GROUP_CLIENTS, EVENT_CLIENT_CONNECTION), EVENT_STATE_CONNECTED);
+ } else {
+ triggerChannel(getChannelID(CHANNEL_GROUP_NETWORK, EVENT_CLIENT_CONNECTION), EVENT_STATE_GONE);
+ router.fireEvent(getChannelID(CHANNEL_GROUP_CLIENTS, EVENT_CLIENT_CONNECTION), EVENT_STATE_GONE);
+ }
+ }
+ }
+
+ private void refreshData() {
+ String mac = getMac();
+ AsuswrtClientInfo clientInfo = router.getClients().getClientByMAC(mac);
+ fireEvents(clientInfo);
+ updateClientProperties(clientInfo);
+ updateClientChannels(clientInfo);
+ updateTrafficChannels(clientInfo.getTraffic());
+ }
+
+ /*
+ * Functions
+ */
+
+ /**
+ * Gets the MAC address of a client from properties or settings.
+ */
+ public String getMac() {
+ String mac = "";
+ Map<String, String> properties = getThing().getProperties();
+ Configuration config = getThing().getConfiguration();
+
+ /* get mac from properties */
+ if (properties.containsKey(Thing.PROPERTY_MAC_ADDRESS)) {
+ mac = config.get(Thing.PROPERTY_MAC_ADDRESS).toString();
+ }
+
+ /* get mac from config */
+ if (mac.isBlank() && config.containsKey(Thing.PROPERTY_MAC_ADDRESS)) {
+ mac = config.get(Thing.PROPERTY_MAC_ADDRESS).toString();
+ }
+
+ if (mac.isBlank()) {
+ logger.debug("({}) cant find macAddress in properties and config", uid);
+ }
+ return AsuswrtUtils.formatMac(mac, ':');
+ }
+
+ /**
+ * Gets the channel ID including the group.
+ */
+ protected String getChannelID(String group, String channel) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+ if (CHANNEL_GROUP_THING_SET.contains(thingTypeUID) && group.length() > 0) {
+ return group + "#" + channel;
+ }
+ return channel;
+ }
+
+ /**
+ * Gets a channel name from a channel ID.
+ */
+ protected String getChannelFromID(ChannelUID channelID) {
+ String channel = channelID.getIdWithoutGroup();
+ channel = channel.replace(CHANNEL_GROUP_CLIENT + "#", "");
+ return channel;
+ }
+
+ /**
+ * Checks if the state changed since the last channel update.
+ *
+ * @param stateName the name of the state (channel)
+ * @param comparator comparison value
+ * @return <code>true</code> if changed, <code>false</code> if not or no old value exists
+ */
+ private Boolean checkForStateChange(String stateName, Object comparator) {
+ if (oldStates.get(stateName) == null) {
+ oldStates.put(stateName, comparator);
+ } else if (!comparator.equals(oldStates.get(stateName))) {
+ oldStates.put(stateName, comparator);
+ return true;
+ }
+ return false;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.things;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtIpInfo;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtTraffic;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AsuswrtInterface} is used as {@link org.openhab.core.thing.binding.ThingHandler ThingHandler} for router
+ * interfaces.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtInterface extends BaseThingHandler {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtInterface.class);
+ private final AsuswrtRouter router;
+ private String ifName = "";
+ private Map<String, Object> oldStates = new HashMap<>();
+ protected final String uid;
+
+ public AsuswrtInterface(Thing thing, AsuswrtRouter router) {
+ super(thing);
+ this.router = router;
+ this.uid = getThing().getUID().getAsString();
+ }
+
+ @Override
+ public void initialize() {
+ logger.trace("({}) Initializing thing ", uid);
+ Configuration config = getThing().getConfiguration();
+ if (config.containsKey(PROPERTY_INTERFACE_NAME)) {
+ this.ifName = config.get(PROPERTY_INTERFACE_NAME).toString();
+ updateProperty(NETWORK_REPRESENTATION_PROPERTY, ifName);
+ updateChannels();
+ updateStatus(ThingStatus.ONLINE);
+ } else {
+ logger.debug("({}) configurtation error", uid);
+ }
+ }
+
+ /*
+ * Commands and events
+ */
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType) {
+ updateChannels();
+ }
+ }
+
+ public void updateChannels() {
+ try {
+ AsuswrtIpInfo interfaceInfo = router.getInterfaces().getByName(ifName);
+ fireEvents(interfaceInfo);
+ updateInterfaceChannels(interfaceInfo);
+ updateTrafficChannels(interfaceInfo.getTraffic());
+ } catch (Exception e) {
+ logger.debug("({}) unable to refresh data - property interfaceName not found ", uid);
+ }
+ }
+
+ private void updateInterfaceChannels(AsuswrtIpInfo interfaceInfo) {
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_MAC), getStringType(interfaceInfo.getMAC()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_IP),
+ getStringType(interfaceInfo.getIpAddress()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_MASK),
+ getStringType(interfaceInfo.getSubnet()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_GATEWAY),
+ getStringType(interfaceInfo.getGateway()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_METHOD),
+ getStringType(interfaceInfo.getIpProto()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_DNS),
+ getStringType(interfaceInfo.getDNSNServer()));
+ updateState(getChannelID(CHANNEL_GROUP_NETWORK, CHANNEL_NETWORK_STATE),
+ getOnOffType(interfaceInfo.isConnected()));
+ }
+
+ private void updateTrafficChannels(AsuswrtTraffic traffic) {
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_CURRENT_RX),
+ getQuantityType(traffic.getCurrentRX(), Units.MEGABIT_PER_SECOND));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_CURRENT_TX),
+ getQuantityType(traffic.getCurrentTX(), Units.MEGABIT_PER_SECOND));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_TODAY_RX),
+ getQuantityType(traffic.getTodayRX(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_TODAY_TX),
+ getQuantityType(traffic.getTodayTX(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_TOTAL_RX),
+ getQuantityType(traffic.getTotalRX(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_TRAFFIC, CHANNEL_TRAFFIC_TOTAL_TX),
+ getQuantityType(traffic.getTotalTX(), Units.MEGABYTE));
+ }
+
+ /**
+ * Fires events on {@link AsuswrtIpInfo} changes.
+ */
+ public void fireEvents(AsuswrtIpInfo interfaceInfo) {
+ Boolean isConnected = interfaceInfo.isConnected();
+ if (checkForStateChange(CHANNEL_NETWORK_STATE, isConnected)) {
+ if (isConnected) {
+ triggerChannel(getChannelID(CHANNEL_GROUP_NETWORK, EVENT_CONNECTION), EVENT_STATE_CONNECTED);
+ } else {
+ triggerChannel(getChannelID(CHANNEL_GROUP_NETWORK, EVENT_CONNECTION), EVENT_STATE_DISCONNECTED);
+ }
+ }
+ }
+
+ /*
+ * Functions
+ */
+
+ /**
+ * Gets the channel ID including the group.
+ */
+ protected String getChannelID(String group, String channel) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+ if (CHANNEL_GROUP_THING_SET.contains(thingTypeUID) && group.length() > 0) {
+ return group + "#" + channel;
+ }
+ return channel;
+ }
+
+ /**
+ * Gets a channel name from a channel ID.
+ */
+ protected String getChannelFromID(ChannelUID channelID) {
+ String channel = channelID.getIdWithoutGroup();
+ channel = channel.replace(CHANNEL_GROUP_NETWORK + "#", "");
+ return channel;
+ }
+
+ /**
+ * Checks if the state changed since the last channel update.
+ *
+ * @param stateName the name of the state (channel)
+ * @param comparator comparison value
+ * @return <code>true</code> if changed, <code>false</code> if not or no old value exists
+ */
+ private Boolean checkForStateChange(String stateName, Object comparator) {
+ if (oldStates.get(stateName) == null) {
+ oldStates.put(stateName, comparator);
+ } else if (!comparator.equals(oldStates.get(stateName))) {
+ oldStates.put(stateName, comparator);
+ return true;
+ }
+ return false;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2023 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.asuswrt.internal.things;
+
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingConstants.*;
+import static org.openhab.binding.asuswrt.internal.constants.AsuswrtBindingSettings.*;
+import static org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils.*;
+
+import java.util.Collection;
+import java.util.List;
+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.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.asuswrt.internal.AsuswrtDiscoveryService;
+import org.openhab.binding.asuswrt.internal.api.AsuswrtConnector;
+import org.openhab.binding.asuswrt.internal.helpers.AsuswrtErrorHandler;
+import org.openhab.binding.asuswrt.internal.helpers.AsuswrtUtils;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtClientList;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtConfiguration;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtInterfaceList;
+import org.openhab.binding.asuswrt.internal.structures.AsuswrtRouterInfo;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.Bridge;
+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.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonObject;
+
+/**
+ * The {@link AsuswrtRouter} is responsible for handling commands, which are sent to one of the channels.
+ *
+ * @author Christian Wild - Initial contribution
+ */
+@NonNullByDefault
+public class AsuswrtRouter extends BaseBridgeHandler {
+ private final Logger logger = LoggerFactory.getLogger(AsuswrtRouter.class);
+
+ private @Nullable ScheduledFuture<?> startupJob;
+ private @Nullable ScheduledFuture<?> pollingJob;
+ private @Nullable ScheduledFuture<?> reconnectJob;
+ private @Nullable ScheduledFuture<?> discoveryJob;
+ private @NonNullByDefault({}) AsuswrtDiscoveryService discoveryService;
+ private @Nullable AsuswrtConnector connector;
+ private AsuswrtConfiguration config;
+ private AsuswrtRouterInfo deviceInfo;
+ private AsuswrtInterfaceList interfaceList = new AsuswrtInterfaceList();
+ private AsuswrtClientList clientList = new AsuswrtClientList();
+ private final HttpClient httpClient;
+ private final String uid;
+
+ public AsuswrtErrorHandler errorHandler;
+
+ public AsuswrtRouter(Bridge bridge, HttpClient httpClient) {
+ super(bridge);
+ Thing thing = getThing();
+ uid = thing.getUID().toString();
+ errorHandler = new AsuswrtErrorHandler();
+ this.httpClient = httpClient;
+ deviceInfo = new AsuswrtRouterInfo();
+ config = new AsuswrtConfiguration();
+ }
+
+ @Override
+ public void initialize() {
+ config = getConfigAs(AsuswrtConfiguration.class);
+ connector = new AsuswrtConnector(this);
+
+ // Initialize the handler.
+ setState(ThingStatus.UNKNOWN);
+
+ // background initialization (delay it a little bit):
+ startupJob = scheduler.schedule(this::delayedStartUp, 1000, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public void dispose() {
+ stopScheduler(startupJob);
+ stopScheduler(pollingJob);
+ stopScheduler(discoveryJob);
+ stopScheduler(reconnectJob);
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return List.of(AsuswrtDiscoveryService.class);
+ }
+
+ public void setDiscoveryService(AsuswrtDiscoveryService discoveryService) {
+ this.discoveryService = discoveryService;
+ }
+
+ /*
+ * Scheduler
+ */
+
+ /**
+ * Delayed one-time startup job.
+ */
+ private void delayedStartUp() {
+ connect();
+ }
+
+ public void startPollingJob() {
+ int pollingInterval = AsuswrtUtils.getValueOrDefault(config.pollingInterval, POLLING_INTERVAL_S_DEFAULT);
+ TimeUnit timeUnit = TimeUnit.SECONDS;
+ if (pollingInterval > 0) {
+ if (pollingInterval < POLLING_INTERVAL_S_MIN) {
+ pollingInterval = POLLING_INTERVAL_S_MIN;
+ }
+ logger.trace("({}) start polling scheduler with interval {} {}", getUID(), pollingInterval, timeUnit);
+ pollingJob = scheduler.scheduleWithFixedDelay(this::pollingJobAction, pollingInterval, pollingInterval,
+ timeUnit);
+ } else {
+ stopScheduler(pollingJob);
+ }
+ }
+
+ protected void pollingJobAction() {
+ if (ThingStatus.ONLINE.equals(getState())) {
+ queryDeviceData();
+ }
+ }
+
+ protected void startReconnectScheduler() {
+ int pollingInterval = config.reconnectInterval;
+ TimeUnit timeUnit = TimeUnit.SECONDS;
+ if (pollingInterval < RECONNECT_INTERVAL_S) {
+ pollingInterval = RECONNECT_INTERVAL_S;
+ }
+ logger.trace("({}) start reconnect scheduler in {} {}", getUID(), pollingInterval, timeUnit);
+ reconnectJob = scheduler.schedule(this::reconnectJobAction, pollingInterval, timeUnit);
+ }
+
+ protected void reconnectJobAction() {
+ connect();
+ }
+
+ protected void startDiscoveryScheduler() {
+ int pollingInterval = config.discoveryInterval;
+ TimeUnit timeUnit = TimeUnit.SECONDS;
+ if (config.autoDiscoveryEnabled && pollingInterval > 0) {
+ logger.trace("{} starting bridge discovery sheduler with interval {} {}", getUID(), pollingInterval,
+ timeUnit);
+ discoveryJob = scheduler.scheduleWithFixedDelay(discoveryService::startScan, 0, pollingInterval, timeUnit);
+ } else {
+ stopScheduler(discoveryJob);
+ }
+ }
+
+ /**
+ * Stops a scheduler.
+ *
+ * @param scheduler ScheduledFeature<?> which should be stopped
+ */
+ protected void stopScheduler(@Nullable ScheduledFuture<?> scheduler) {
+ if (scheduler != null) {
+ logger.trace("{} stopping scheduler {}", uid, scheduler);
+ scheduler.cancel(true);
+ }
+ }
+
+ /*
+ * Functions
+ */
+
+ /**
+ * Connects to the router and sets the states.
+ */
+ @SuppressWarnings("null")
+ protected void connect() {
+ connector.login();
+ if (connector.cookieStore.cookieIsSet()) {
+ stopScheduler(reconnectJob);
+ queryDeviceData(false);
+ devicePropertiesChanged(deviceInfo);
+ setState(ThingStatus.ONLINE);
+ startPollingJob();
+ } else {
+ setState(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorHandler.getErrorMessage());
+ }
+ }
+
+ @SuppressWarnings("null")
+ public void queryDeviceData(Boolean asyncRequest) {
+ connector.queryDeviceData(CMD_GET_SYSINFO + CMD_GET_USAGE + CMD_GET_LANINFO + CMD_GET_WANINFO
+ + CMD_GET_CLIENTLIST + CMD_GET_TRAFFIC, asyncRequest);
+ }
+
+ /**
+ * Queries device data asynchronously.
+ */
+ public void queryDeviceData() {
+ queryDeviceData(true);
+ }
+
+ /**
+ * Sets routerInfo data and updates channels on receiving new data with the associated command.
+ *
+ * @param jsonObject contains the received data
+ * @param command the command that was sent
+ */
+ public void dataReceived(JsonObject jsonObject, String command) {
+ if (command.contains(CMD_GET_SYSINFO)) {
+ deviceInfo.setSysInfo(jsonObject);
+ devicePropertiesChanged(deviceInfo);
+ }
+ if (command.contains(CMD_GET_CLIENTLIST)) {
+ clientList.setData(jsonObject);
+ updateClients();
+ }
+ if (command.contains(CMD_GET_LANINFO)) {
+ interfaceList.setData(INTERFACE_LAN, jsonObject);
+ updateChild(THING_TYPE_INTERFACE, NETWORK_REPRESENTATION_PROPERTY, INTERFACE_LAN);
+ }
+ if (command.contains(CMD_GET_WANINFO)) {
+ interfaceList.setData(INTERFACE_WAN, jsonObject);
+ updateChild(THING_TYPE_INTERFACE, NETWORK_REPRESENTATION_PROPERTY, INTERFACE_WAN);
+ }
+ if (command.contains(CMD_GET_USAGE) || command.contains(CMD_GET_MEMUSAGE)
+ || command.contains(CMD_GET_CPUUSAGE)) {
+ deviceInfo.setUsageStats(jsonObject);
+ }
+ updateChannels(deviceInfo, clientList);
+ }
+
+ /**
+ * Updates the router status.
+ */
+ public void setState(ThingStatus thingStatus, ThingStatusDetail statusDetail, String text) {
+ if (!thingStatus.equals(getThing().getStatus())) {
+ updateStatus(thingStatus, statusDetail, text);
+ updateChildStates(thingStatus);
+ if (ThingStatus.OFFLINE.equals(thingStatus)) {
+ stopScheduler(pollingJob);
+ // Set channels to undef
+ getThing().getChannels().forEach(c -> updateState(c.getUID(), UnDefType.UNDEF));
+ startReconnectScheduler();
+ }
+ }
+ }
+
+ /**
+ * Upate RouterStatus
+ */
+ public void setState(ThingStatus thingStatus) {
+ setState(thingStatus, ThingStatusDetail.NONE, "");
+ }
+
+ /***********************************
+ *
+ * PUBLIC GETs
+ *
+ ************************************/
+
+ public HttpClient getHttpClient() {
+ return httpClient;
+ }
+
+ public AsuswrtConfiguration getConfiguration() {
+ return config;
+ }
+
+ public AsuswrtErrorHandler getErrorHandler() {
+ return errorHandler;
+ }
+
+ public ThingUID getUID() {
+ return thing.getUID();
+ }
+
+ public AsuswrtRouterInfo getDeviceInfo() {
+ return deviceInfo;
+ }
+
+ public AsuswrtClientList getClients() {
+ return clientList;
+ }
+
+ public AsuswrtInterfaceList getInterfaces() {
+ return interfaceList;
+ }
+
+ public ThingStatus getState() {
+ return getThing().getStatus();
+ }
+
+ /***********************************
+ *
+ * COMMAND HANDLER
+ *
+ ************************************/
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ if (command instanceof RefreshType) {
+ queryDeviceData();
+ }
+ }
+
+ /***********************************
+ *
+ * PROPERTIES
+ *
+ ************************************/
+
+ /**
+ * UPDATE PROPERTIES
+ *
+ * If only one property must be changed, there is also a convenient method
+ * updateProperty(String name, String value).
+ */
+ public void devicePropertiesChanged(AsuswrtRouterInfo deviceInfo) {
+ /* device properties */
+ Map<String, String> properties = editProperties();
+ properties.put(Thing.PROPERTY_MAC_ADDRESS, interfaceList.getByName(INTERFACE_WAN).getMAC());
+ properties.put(Thing.PROPERTY_MODEL_ID, deviceInfo.getProductId());
+ properties.put(Thing.PROPERTY_FIRMWARE_VERSION, deviceInfo.getFirmwareVersion());
+ updateProperties(properties);
+ }
+
+ /***********************************
+ *
+ * CHANNELS
+ *
+ ************************************/
+
+ /**
+ * Update all Channels
+ */
+ public void updateChannels(AsuswrtRouterInfo deviceInfo, AsuswrtClientList clientList) {
+ updateClientChannels(clientList);
+ updateUsageChannels(deviceInfo);
+ }
+
+ /**
+ * Update Channel Usage
+ */
+ public void updateUsageChannels(AsuswrtRouterInfo deviceInfo) {
+ updateState(getChannelID(CHANNEL_GROUP_SYSINFO, CHANNEL_MEM_TOTAL),
+ getQuantityType(deviceInfo.getMemUsage().getTotal(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_SYSINFO, CHANNEL_MEM_FREE),
+ getQuantityType(deviceInfo.getMemUsage().getFree(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_SYSINFO, CHANNEL_MEM_USED),
+ getQuantityType(deviceInfo.getMemUsage().getUsed(), Units.MEGABYTE));
+ updateState(getChannelID(CHANNEL_GROUP_SYSINFO, CHANNEL_MEM_FREE_PERCENT),
+ getQuantityType(deviceInfo.getMemUsage().getFreePercent(), Units.PERCENT));
+ updateState(getChannelID(CHANNEL_GROUP_SYSINFO, CHANNEL_MEM_USED_PERCENT),
+ getQuantityType(deviceInfo.getMemUsage().getUsedPercent(), Units.PERCENT));
+ updateState(getChannelID(CHANNEL_GROUP_SYSINFO, CHANNEL_CPU_USED_PERCENT),
+ getQuantityType(deviceInfo.getCpuAverage().getUsedPercent(), Units.PERCENT));
+ }
+
+ /**
+ * Update Client Channel
+ */
+ public void updateClientChannels(AsuswrtClientList clientList) {
+ updateState(getChannelID(CHANNEL_GROUP_CLIENTS, CHANNEL_CLIENTS_KNOWN),
+ getStringType(clientList.getClientList()));
+ updateState(getChannelID(CHANNEL_GROUP_CLIENTS, CHANNEL_CLIENTS_ONLINE),
+ getStringType(clientList.getOnlineClients().getClientList()));
+ updateState(getChannelID(CHANNEL_GROUP_CLIENTS, CHANNEL_CLIENTS_COUNT),
+ getDecimalType(clientList.getOnlineClients().getCount()));
+ updateState(getChannelID(CHANNEL_GROUP_CLIENTS, CHANNEL_CLIENTS_ONLINE_MAC),
+ getStringType(clientList.getOnlineClients().getMacAddresses()));
+ }
+
+ /**
+ * Fire Event
+ *
+ * @param channelUID chanelUID event belongs to
+ * @param event event-name is fired
+ */
+ protected void fireEvent(String channel, String event) {
+ triggerChannel(channel, event);
+ }
+
+ /***********************************
+ *
+ * CHILD THINGS
+ *
+ ************************************/
+ /**
+ * Update all Child-Things with type Client
+ */
+ public void updateClients() {
+ updateChildThings(THING_TYPE_CLIENT);
+ }
+
+ /**
+ * Update all Child-Things with type Interface
+ */
+ public void updateInterfaces() {
+ updateChildThings(THING_TYPE_INTERFACE);
+ }
+
+ /**
+ * Update all Child-Things belonging to ThingTypeUID
+ */
+ public void updateChildThings(ThingTypeUID thingTypeToUpdate) {
+ ThingTypeUID thingTypeUID;
+ List<Thing> things = getThing().getThings();
+ for (Thing thing : things) {
+ thingTypeUID = thing.getThingTypeUID();
+ if (thingTypeToUpdate.equals(thingTypeUID)) {
+ updateChild(thing);
+ }
+ }
+ }
+
+ /**
+ * Update Child single child with special representationProperty
+ *
+ * @param thingTypeToUpdate ThingTypeUID of Thing to update
+ * @param representationProperty Name of representationProperty
+ * @param propertyValue Value of representationProperty
+ */
+ public void updateChild(ThingTypeUID thingTypeToUpdate, String representationProperty, String propertyValue) {
+ List<Thing> things = getThing().getThings();
+ for (Thing thing : things) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+ if (thingTypeToUpdate.equals(thingTypeUID)) {
+ String thingProperty = thing.getProperties().get(representationProperty);
+ if (propertyValue.equals(thingProperty)) {
+ updateChild(thing);
+ }
+ }
+ }
+ }
+
+ /**
+ * Update Child-Thing (send refreshCommand)
+ *
+ * @param thing - Thing to update
+ */
+ public void updateChild(Thing thing) {
+ ThingHandler handler = thing.getHandler();
+ if (handler != null) {
+ ChannelUID cUid = new ChannelUID(thing.getUID(), CHANNELS_ALL);
+ handler.handleCommand(cUid, RefreshType.REFRESH);
+ }
+ }
+
+ /**
+ * Set State of all clients
+ *
+ * @param thingStatus new ThingStatus
+ */
+ public void updateChildStates(ThingStatus thingStatus) {
+ List<Thing> things = getThing().getThings();
+ for (Thing thing : things) {
+ updateChildState(thing, thingStatus);
+ }
+ }
+
+ /**
+ * Set State of a Thing
+ *
+ * @param thing Thing to update
+ * @param thingStatus new ThingStatus
+ */
+ public void updateChildState(Thing thing, ThingStatus thingStatus) {
+ ThingHandler handler = thing.getHandler();
+ if (handler != null) {
+ if (ThingStatus.OFFLINE.equals(thingStatus)) {
+ handler.bridgeStatusChanged(new ThingStatusInfo(thingStatus, ThingStatusDetail.BRIDGE_OFFLINE, ""));
+ } else {
+ handler.bridgeStatusChanged(new ThingStatusInfo(thingStatus, ThingStatusDetail.NONE, ""));
+ }
+ }
+ }
+
+ /***********************************
+ *
+ * FUNCTIONS
+ *
+ ************************************/
+
+ /**
+ * Get ChannelID including group
+ *
+ * @param group String channel-group
+ * @param channel String channel-name
+ * @return String channelID
+ */
+ protected String getChannelID(String group, String channel) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+ if (CHANNEL_GROUP_THING_SET.contains(thingTypeUID) && group.length() > 0) {
+ return group + "#" + channel;
+ }
+ return channel;
+ }
+
+ /**
+ * Get Channel from ChannelID
+ *
+ * @param channelID String channelID
+ * @return String channel-name
+ */
+ protected String getChannelFromID(ChannelUID channelID) {
+ String channel = channelID.getIdWithoutGroup();
+ channel = channel.replace(CHANNEL_GROUP_SYSINFO + "#", "");
+ channel = channel.replace(CHANNEL_GROUP_CLIENTS + "#", "");
+ return channel;
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<addon:addon id="asuswrt" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
+ <type>binding</type>
+ <name>Asuswrt Binding</name>
+ <description>Binding for ASUS routers (Asuswrt / Asuswrt-Merlin only)</description>
+ <connection>local</connection>
+</addon:addon>
--- /dev/null
+# add-on
+
+addon.asuswrt.name = Asuswrt Binding
+addon.asuswrt.description = Binding for ASUS routers (Asuswrt / Asuswrt-Merlin only)
+
+# thing types
+
+thing-type.asuswrt.client.label = Asuswrt Client
+thing-type.asuswrt.client.description = Client connected to Asuswrt-Router
+thing-type.asuswrt.interface.label = Router Interface
+thing-type.asuswrt.interface.description = Interface of router
+thing-type.asuswrt.router.label = Asuswrt Router
+thing-type.asuswrt.router.description = Router with Asuswrt or Asuswrt-Merlin OS
+
+# thing types config
+
+thing-type.config.asuswrt.client.clientNick.label = Nickname
+thing-type.config.asuswrt.client.clientNick.description = Nickname of the device
+thing-type.config.asuswrt.client.macAddress.label = MAC Address
+thing-type.config.asuswrt.client.macAddress.description = MAC address of the device
+thing-type.config.asuswrt.interface.interfaceName.label = Interface Name
+thing-type.config.asuswrt.interface.interfaceName.description = Name of selected interface. *Maybe not all are supported by your device*
+thing-type.config.asuswrt.interface.interfaceName.option.wan = WAN
+thing-type.config.asuswrt.interface.interfaceName.option.lan = LAN
+thing-type.config.asuswrt.router.hostname.label = Hostname
+thing-type.config.asuswrt.router.hostname.description = Hostname or IP address of the device
+thing-type.config.asuswrt.router.httpPort.label = HTTP Port
+thing-type.config.asuswrt.router.httpPort.description = Port used for HTTP connection
+thing-type.config.asuswrt.router.httpsPort.label = HTTPS Port
+thing-type.config.asuswrt.router.httpsPort.description = Port used for HTTPS connection
+thing-type.config.asuswrt.router.password.label = Password
+thing-type.config.asuswrt.router.password.description = Password to access the device
+thing-type.config.asuswrt.router.pollingInterval.label = Polling Interval
+thing-type.config.asuswrt.router.pollingInterval.description = Interval the device is polled in sec.
+thing-type.config.asuswrt.router.useSSL.label = Use SSL
+thing-type.config.asuswrt.router.useSSL.description = Use SSL to authenticate. If not use HTTP
+thing-type.config.asuswrt.router.username.label = Username
+thing-type.config.asuswrt.router.username.description = Username to access the device
+
+# channel group types
+
+channel-group-type.asuswrt.client-list-group.label = Clients
+channel-group-type.asuswrt.client-list-group.description = Clients connected to router
+channel-group-type.asuswrt.client-list-group.channel.known-clients.label = Known Clients
+channel-group-type.asuswrt.client-list-group.channel.known-clients.description = Known clients with name and MAC addresses
+channel-group-type.asuswrt.client-list-group.channel.online-clients.description = Online clients with name and MAC addresses
+channel-group-type.asuswrt.clientNetworkGroup.label = Clients
+channel-group-type.asuswrt.clientNetworkGroup.description = Clients connected to router
+channel-group-type.asuswrt.clientNetworkGroup.channel.ip-address.description = Client IP address
+channel-group-type.asuswrt.if-info-group.label = LAN Status
+channel-group-type.asuswrt.if-info-group.description = LAN connection state
+channel-group-type.asuswrt.sys-info-group.label = System Info
+channel-group-type.asuswrt.sys-info-group.description = System information about the device
+channel-group-type.asuswrt.sys-info-group.channel.cpu-used-percent.label = Total CPU Usage
+channel-group-type.asuswrt.sys-info-group.channel.cpu-used-percent.description = Total CPU usage in percent over all cores
+channel-group-type.asuswrt.sys-info-group.channel.mem-free.label = Free Memory
+channel-group-type.asuswrt.sys-info-group.channel.mem-free.description = Free memory in MB
+channel-group-type.asuswrt.sys-info-group.channel.mem-free-percent.label = Free Memory
+channel-group-type.asuswrt.sys-info-group.channel.mem-free-percent.description = Free memory in %
+channel-group-type.asuswrt.sys-info-group.channel.mem-total.label = Total Memory
+channel-group-type.asuswrt.sys-info-group.channel.mem-total.description = Total memory in MB
+channel-group-type.asuswrt.sys-info-group.channel.mem-used.label = Used Memory
+channel-group-type.asuswrt.sys-info-group.channel.mem-used.description = Used memory in MB
+channel-group-type.asuswrt.sys-info-group.channel.mem-used-percent.label = Used Memory
+channel-group-type.asuswrt.sys-info-group.channel.mem-used-percent.description = Used memory in %
+channel-group-type.asuswrt.traffic-group.label = Traffic
+channel-group-type.asuswrt.traffic-group.description = Traffic Monitoring
+channel-group-type.asuswrt.traffic-group.channel.cur-rx.label = Current Traffic Received
+channel-group-type.asuswrt.traffic-group.channel.cur-tx.label = Current Traffic Sent
+channel-group-type.asuswrt.traffic-group.channel.today-rx.label = Today Traffic Received
+channel-group-type.asuswrt.traffic-group.channel.today-tx.label = Today Traffic Sent
+channel-group-type.asuswrt.traffic-group.channel.total-rx.label = Total Traffic Received
+channel-group-type.asuswrt.traffic-group.channel.total-tx.label = Total Traffic Sent
+
+# channel types
+
+channel-type.asuswrt.client-nick-name.label = Nickname
+channel-type.asuswrt.client-nick-name.description = Nickname of client
+channel-type.asuswrt.client-online-event-type.label = Online State Changed Trigger
+channel-type.asuswrt.client-online-event-type.description = Event is fired if client leaves ('gone') or enters ('connected') the network
+channel-type.asuswrt.clients-online-count-type.label = Online Clients Count
+channel-type.asuswrt.clients-online-count-type.description = number online clients
+channel-type.asuswrt.clients-online-mac-type.label = Online MAC Addresses
+channel-type.asuswrt.clients-online-mac-type.description = List with MAC addresses of online clients
+channel-type.asuswrt.clients-online-type.label = Online Clients
+channel-type.asuswrt.clients-online-type.description = List of online clients
+channel-type.asuswrt.connection-event-type.label = ConnectionState Changed Event
+channel-type.asuswrt.connection-event-type.description = Event is fired connection state changes ('connected'/'disconnected')
+channel-type.asuswrt.current-traffic-type.label = Current Traffic
+channel-type.asuswrt.current-traffic-type.description = Current traffic in MBit/s
+channel-type.asuswrt.dns-name-type.label = DNS Name
+channel-type.asuswrt.dns-name-type.description = DNS name
+channel-type.asuswrt.dns-server-type.label = DNS Server
+channel-type.asuswrt.dns-server-type.description = Used DNS Servers
+channel-type.asuswrt.interne-state-type.label = Internet Connected
+channel-type.asuswrt.interne-state-type.description = Client is connected to Internet
+channel-type.asuswrt.ip-address-type.label = IP Address
+channel-type.asuswrt.ip-address-type.description = IP address of interface
+channel-type.asuswrt.ip-connection-state.label = Connected
+channel-type.asuswrt.ip-connection-state.description = Connection state of interface
+channel-type.asuswrt.ip-gateway-type.label = Gateway
+channel-type.asuswrt.ip-gateway-type.description = Gateway of interface
+channel-type.asuswrt.ip-netmask-type.label = Subnet
+channel-type.asuswrt.ip-netmask-type.description = Subnet mask of interface
+channel-type.asuswrt.ip-proto-type.label = IP Protocol
+channel-type.asuswrt.ip-proto-type.description = IP address protocol (DHCP/Static)
+channel-type.asuswrt.is-online-type.label = Online
+channel-type.asuswrt.is-online-type.description = Client is online
+channel-type.asuswrt.mac-address-type.label = MAC Address
+channel-type.asuswrt.mac-address-type.description = MAC address of device (LAN)
+channel-type.asuswrt.productid.label = Router Model
+channel-type.asuswrt.productid.description = Model/ProductID of your Router
+channel-type.asuswrt.today-taffic-type.label = Today Traffic
+channel-type.asuswrt.today-taffic-type.description = Total traffic in MB since 0:00 a clock
+channel-type.asuswrt.total-traffic-type.label = Total Traffic
+channel-type.asuswrt.total-traffic-type.description = Total traffic in MB since reboot
+channel-type.asuswrt.usage-data-type.label = Usage in MB
+channel-type.asuswrt.usage-type-percent.label = Percentage
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="asuswrt"
+ 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">
+
+ <!-- ####################### GLOBAL CHANNEL GROUPS ########################## -->
+
+ <!-- Traffic -->
+ <channel-group-type id="traffic-group">
+ <label>Traffic</label>
+ <description>Traffic Monitoring</description>
+ <channels>
+ <channel id="cur-rx" typeId="current-traffic-type">
+ <label>Current Traffic Received</label>
+ </channel>
+ <channel id="cur-tx" typeId="current-traffic-type">
+ <label>Current Traffic Sent</label>
+ </channel>
+ <channel id="today-rx" typeId="today-taffic-type">
+ <label>Today Traffic Received</label>
+ </channel>
+ <channel id="today-tx" typeId="today-taffic-type">
+ <label>Today Traffic Sent</label>
+ </channel>
+ <channel id="total-rx" typeId="total-traffic-type">
+ <label>Total Traffic Received</label>
+ </channel>
+ <channel id="total-tx" typeId="total-traffic-type">
+ <label>Total Traffic Sent</label>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+
+ <!-- ############################### CHANNELS ############################### -->
+
+ <!-- Product Id -->
+ <channel-type id="productid">
+ <item-type>String</item-type>
+ <label>Router Model</label>
+ <description>Model/ProductID of your Router</description>
+ </channel-type>
+
+ <!-- Usage -->
+ <channel-type id="usage-data-type">
+ <item-type>Number:DataAmount</item-type>
+ <label>Usage in MB</label>
+ <state pattern="%.0f %unit%" readOnly="true"></state>
+ </channel-type>
+ <channel-type id="usage-type-percent">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Percentage</label>
+ <state pattern="%.1f %unit%" readOnly="true"></state>
+ </channel-type>
+
+
+ <!-- LAN/WAN INFO -->
+ <channel-type id="dns-name-type">
+ <item-type>String</item-type>
+ <label>DNS Name</label>
+ <description>DNS name</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="dns-server-type">
+ <item-type>String</item-type>
+ <label>DNS Server</label>
+ <description>Used DNS Servers</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="ip-address-type">
+ <item-type>String</item-type>
+ <label>IP Address</label>
+ <description>IP address of interface</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="ip-proto-type">
+ <item-type>String</item-type>
+ <label>IP Protocol</label>
+ <description>IP address protocol (DHCP/Static)</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="mac-address-type">
+ <item-type>String</item-type>
+ <label>MAC Address</label>
+ <description>MAC address of device (LAN)</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="ip-netmask-type">
+ <item-type>String</item-type>
+ <label>Subnet</label>
+ <description>Subnet mask of interface</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="ip-gateway-type">
+ <item-type>String</item-type>
+ <label>Gateway</label>
+ <description>Gateway of interface</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="ip-connection-state">
+ <item-type>Switch</item-type>
+ <label>Connected</label>
+ <description>Connection state of interface</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="connection-event-type">
+ <kind>Trigger</kind>
+ <label>ConnectionState Changed Event</label>
+ <description>Event is fired connection state changes ('connected'/'disconnected')</description>
+ </channel-type>
+
+ <!-- CLIENTS -->
+ <channel-type id="clients-online-type">
+ <item-type>String</item-type>
+ <label>Online Clients</label>
+ <description>List of online clients</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="clients-online-mac-type">
+ <item-type>String</item-type>
+ <label>Online MAC Addresses</label>
+ <description>List with MAC addresses of online clients</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="clients-online-count-type">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Online Clients Count</label>
+ <description>number online clients</description>
+ <state pattern="%.0f" readOnly="true"></state>
+ </channel-type>
+ <channel-type id="client-nick-name">
+ <item-type>String</item-type>
+ <label>Nickname</label>
+ <description>Nickname of client</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="is-online-type">
+ <item-type>Switch</item-type>
+ <label>Online</label>
+ <description>Client is online</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="interne-state-type">
+ <item-type>Switch</item-type>
+ <label>Internet Connected</label>
+ <description>Client is connected to Internet</description>
+ <state readOnly="true"></state>
+ </channel-type>
+ <channel-type id="client-online-event-type">
+ <kind>Trigger</kind>
+ <label>Online State Changed Trigger</label>
+ <description>Event is fired if client leaves ('gone') or enters ('connected') the network</description>
+ </channel-type>
+
+
+ <!-- Traffic -->
+ <channel-type id="current-traffic-type">
+ <item-type>Number:DataTransferRate</item-type>
+ <label>Current Traffic</label>
+ <description>Current traffic in MBit/s</description>
+ <state pattern="%.2f %unit%" readOnly="true"></state>
+ </channel-type>
+
+ <channel-type id="today-taffic-type">
+ <item-type>Number:DataAmount</item-type>
+ <label>Today Traffic</label>
+ <description>Total traffic in MB since 0:00 a clock</description>
+ <state pattern="%.0f %unit%" readOnly="true"></state>
+ </channel-type>
+
+ <channel-type id="total-traffic-type">
+ <item-type>Number:DataAmount</item-type>
+ <label>Total Traffic</label>
+ <description>Total traffic in MB since reboot</description>
+ <state pattern="%.0f %unit%" readOnly="true"></state>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="asuswrt"
+ 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">
+
+ <!-- Router Thing Type -->
+ <thing-type id="client">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="router"/>
+ </supported-bridge-type-refs>
+
+ <label>Asuswrt Client</label>
+ <description>Client connected to Asuswrt-Router</description>
+ <channel-groups>
+ <channel-group id="network-info" typeId="clientNetworkGroup"></channel-group>
+ <channel-group id="traffic" typeId="traffic-group"></channel-group>
+ </channel-groups>
+ <properties>
+ <property name="vendor">Vendor</property>
+ <property name="dnsName">DNS Name</property>
+ </properties>
+ <representation-property>macAddress</representation-property>
+
+ <config-description>
+ <parameter name="macAddress" type="text" required="true">
+ <label>MAC Address</label>
+ <description>MAC address of the device</description>
+ <default>00:00:00:00:00:00</default>
+ </parameter>
+ <parameter name="clientNick" type="text" required="false">
+ <label>Nickname</label>
+ <description>Nickname of the device</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- ############################### CHANNEL-GROUPS ############################### -->
+
+ <channel-group-type id="clientNetworkGroup">
+ <label>Clients</label>
+ <description>Clients connected to router</description>
+ <channels>
+ <channel id="network-state" typeId="is-online-type"></channel>
+ <channel id="ip-address" typeId="ip-address-type">
+ <description>Client IP address</description>
+ </channel>
+ <channel id="ip-method" typeId="ip-proto-type"></channel>
+ <channel id="internet-state" typeId="interne-state-type"></channel>
+ <channel id="client-online-event" typeId="client-online-event-type"></channel>
+ </channels>
+ </channel-group-type>
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="asuswrt"
+ 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">
+
+ <!-- Router Thing Type -->
+ <thing-type id="interface">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="router"/>
+ </supported-bridge-type-refs>
+
+ <label>Router Interface</label>
+ <description>Interface of router</description>
+ <channel-groups>
+ <channel-group id="network-info" typeId="if-info-group"></channel-group>
+ <channel-group id="traffic" typeId="traffic-group"></channel-group>
+ </channel-groups>
+ <representation-property>interfaceName</representation-property>
+
+ <config-description>
+ <parameter name="interfaceName" type="text" required="true">
+ <label>Interface Name</label>
+ <description>Name of selected interface. *Maybe not all are supported by your device*</description>
+ <options>
+ <option value="wan">WAN</option>
+ <option value="lan">LAN</option>
+ </options>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+
+ <!-- ############################### CHANNEL-GROUPS ############################### -->
+
+ <!-- Interface Information -->
+ <channel-group-type id="if-info-group">
+ <label>LAN Status</label>
+ <description>LAN connection state</description>
+ <channels>
+ <channel id="mac-address" typeId="mac-address-type"></channel>
+ <channel id="ip-address" typeId="ip-address-type"></channel>
+ <channel id="ip-method" typeId="ip-proto-type"></channel>
+ <channel id="subnet" typeId="ip-netmask-type"></channel>
+ <channel id="gateway" typeId="ip-gateway-type"></channel>
+ <channel id="dns-servers" typeId="dns-server-type"></channel>
+ <channel id="network-state" typeId="ip-connection-state"></channel>
+ <channel id="connection-event" typeId="connection-event-type"></channel>
+ </channels>
+ </channel-group-type>
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="asuswrt"
+ 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">
+
+ <!-- Router Thing Type -->
+ <bridge-type id="router">
+ <label>Asuswrt Router</label>
+ <description>Router with Asuswrt or Asuswrt-Merlin OS</description>
+ <channel-groups>
+ <channel-group id="sys-info" typeId="sys-info-group"></channel-group>
+ <channel-group id="client-list" typeId="client-list-group"></channel-group>
+ </channel-groups>
+
+ <config-description>
+ <parameter name="hostname" type="text" required="true">
+ <context>network-address</context>
+ <label>Hostname</label>
+ <description>Hostname or IP address of the device</description>
+ <default>router.asus.com</default>
+ </parameter>
+ <parameter name="username" type="text" required="true">
+ <label>Username</label>
+ <description>Username to access the device</description>
+ </parameter>
+ <parameter name="password" type="text" required="true">
+ <context>password</context>
+ <label>Password</label>
+ <description>Password to access the device</description>
+ </parameter>
+ <parameter name="useSSL" type="boolean">
+ <label>Use SSL</label>
+ <description>Use SSL to authenticate. If not use HTTP</description>
+ <default>false</default>
+ </parameter>
+ <parameter name="httpPort" type="integer">
+ <label>HTTP Port</label>
+ <description>Port used for HTTP connection</description>
+ <default>80</default>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="httpsPort" type="integer">
+ <label>HTTPS Port</label>
+ <description>Port used for HTTPS connection</description>
+ <default>443</default>
+ <advanced>true</advanced>
+ </parameter>
+ <parameter name="pollingInterval" type="integer" unit="s" min="3">
+ <label>Polling Interval</label>
+ <description>Interval the device is polled in sec.</description>
+ <default>20</default>
+ <advanced>true</advanced>
+ </parameter>
+ </config-description>
+ </bridge-type>
+
+ <!-- ############################### CHANNEL-GROUPS ############################### -->
+ <!-- System Information -->
+ <channel-group-type id="sys-info-group">
+ <label>System Info</label>
+ <description>System information about the device</description>
+ <channels>
+ <channel id="mem-total" typeId="usage-data-type">
+ <label>Total Memory</label>
+ <description>Total memory in MB</description>
+ </channel>
+ <channel id="mem-used" typeId="usage-data-type">
+ <label>Used Memory</label>
+ <description>Used memory in MB</description>
+ </channel>
+ <channel id="mem-free" typeId="usage-data-type">
+ <label>Free Memory</label>
+ <description>Free memory in MB</description>
+ </channel>
+ <channel id="mem-free-percent" typeId="usage-type-percent">
+ <label>Free Memory</label>
+ <description>Free memory in %</description>
+ </channel>
+ <channel id="mem-used-percent" typeId="usage-type-percent">
+ <label>Used Memory</label>
+ <description>Used memory in %</description>
+ </channel>
+ <channel id="cpu-used-percent" typeId="usage-type-percent">
+ <label>Total CPU Usage</label>
+ <description>Total CPU usage in percent over all cores</description>
+ </channel>
+ </channels>
+ </channel-group-type>
+
+ <!-- Clients -->
+ <channel-group-type id="client-list-group">
+ <label>Clients</label>
+ <description>Clients connected to router</description>
+ <channels>
+ <channel id="known-clients" typeId="clients-online-type">
+ <label>Known Clients</label>
+ <description>Known clients with name and MAC addresses</description>
+ </channel>
+ <channel id="online-clients" typeId="clients-online-type">
+ <description>Online clients with name and MAC addresses</description>
+ </channel>
+ <channel id="online-macs" typeId="clients-online-mac-type"></channel>
+ <channel id="online-clients-count" typeId="clients-online-count-type"></channel>
+ <channel id="client-online-event" typeId="client-online-event-type"></channel>
+ </channels>
+ </channel-group-type>
+</thing:thing-descriptions>
<module>org.openhab.binding.anel</module>
<module>org.openhab.binding.anthem</module>
<module>org.openhab.binding.astro</module>
+ <module>org.openhab.binding.asuswrt</module>
<module>org.openhab.binding.atlona</module>
<module>org.openhab.binding.autelis</module>
<module>org.openhab.binding.automower</module>