diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 28893eca65d64..1a74f87bc94cf 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -121,6 +121,11 @@
org.openhab.binding.astro
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.asuswrt
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.atlona
diff --git a/bundles/org.openhab.binding.asuswrt/NOTICE b/bundles/org.openhab.binding.asuswrt/NOTICE
new file mode 100644
index 0000000000000..38d625e349232
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.asuswrt/README.md b/bundles/org.openhab.binding.asuswrt/README.md
new file mode 100644
index 0000000000000..39dd664f304f4
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/README.md
@@ -0,0 +1,91 @@
+# 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 |
diff --git a/bundles/org.openhab.binding.asuswrt/pom.xml b/bundles/org.openhab.binding.asuswrt/pom.xml
new file mode 100644
index 0000000000000..219096837055a
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.0.0-SNAPSHOT
+
+
+ org.openhab.binding.asuswrt
+
+ openHAB Add-ons :: Bundles :: Asuswrt Binding
+
+
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/feature/feature.xml b/bundles/org.openhab.binding.asuswrt/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..73416289caf02
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.asuswrt/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/AsuswrtDiscoveryService.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/AsuswrtDiscoveryService.java
new file mode 100644
index 0000000000000..252ced30bf16e
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/AsuswrtDiscoveryService.java
@@ -0,0 +1,212 @@
+/**
+ * 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 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 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();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/AsuswrtHandlerFactory.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/AsuswrtHandlerFactory.java
new file mode 100644
index 0000000000000..b52d3d0ecc1bc
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/AsuswrtHandlerFactory.java
@@ -0,0 +1,126 @@
+/**
+ * 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 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtConnector.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtConnector.java
new file mode 100644
index 0000000000000..f159881ec4a22
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtConnector.java
@@ -0,0 +1,199 @@
+/**
+ * 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 true
if request should be sent asynchronous, false
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;
+ }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtCookie.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtCookie.java
new file mode 100644
index 0000000000000..3e4c66a37d28f
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtCookie.java
@@ -0,0 +1,84 @@
+/**
+ * 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 true
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;
+ }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtHttpClient.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtHttpClient.java
new file mode 100644
index 0000000000000..e8cd515f0252d
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtHttpClient.java
@@ -0,0 +1,249 @@
+/**
+ * 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();
+ }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtBindingConstants.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtBindingConstants.java
new file mode 100644
index 0000000000000..edcbdd2d0648a
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtBindingConstants.java
@@ -0,0 +1,194 @@
+/**
+ * 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 SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ROUTER, THING_TYPE_CLIENT,
+ THING_TYPE_INTERFACE);
+
+ // Things with channel groups
+ public static final Set 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";
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtBindingSettings.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtBindingSettings.java
new file mode 100644
index 0000000000000..f42798bbd37e9
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtBindingSettings.java
@@ -0,0 +1,63 @@
+/**
+ * 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 INTERFACE_LIST = Set.of(INTERFACE_WAN, INTERFACE_LAN);
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtErrorConstants.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtErrorConstants.java
new file mode 100644
index 0000000000000..78baab6bf33a3
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtErrorConstants.java
@@ -0,0 +1,34 @@
+/**
+ * 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";
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/helpers/AsuswrtErrorHandler.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/helpers/AsuswrtErrorHandler.java
new file mode 100644
index 0000000000000..577566619b014
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/helpers/AsuswrtErrorHandler.java
@@ -0,0 +1,95 @@
+/**
+ * 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;
+ }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/helpers/AsuswrtUtils.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/helpers/AsuswrtUtils.java
new file mode 100644
index 0000000000000..613797fac7897
--- /dev/null
+++ b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/helpers/AsuswrtUtils.java
@@ -0,0 +1,414 @@
+/**
+ * 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 null
.
+ *
+ * @param Type of value
+ * @param value the value which should be checked
+ * @param defaultValue the default value that will be returned when value
is null
+ */
+ public static 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 boolean
.
+ *
+ * @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 int
.
+ *
+ * @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 null
, 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 boolean
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 boolean
value from a {@link JsonObject} using false
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 int
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 int
value from a {@link JsonObject} using 0
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 0
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