]> git.basschouten.com Git - openhab-addons.git/commitdiff
[asuswrt] Initial contribution (#13815)
authorChristian Wild <40909464+wildcs@users.noreply.github.com>
Sat, 15 Jul 2023 12:07:51 +0000 (14:07 +0200)
committerGitHub <noreply@github.com>
Sat, 15 Jul 2023 12:07:51 +0000 (14:07 +0200)
Signed-off-by: Christian Wild <christian@wildclan.de>
34 files changed:
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.asuswrt/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/README.md [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/AsuswrtDiscoveryService.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/AsuswrtHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtConnector.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtCookie.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/api/AsuswrtHttpClient.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtBindingSettings.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/constants/AsuswrtErrorConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/helpers/AsuswrtErrorHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/helpers/AsuswrtUtils.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtClientInfo.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtClientList.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtCredentials.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtInterfaceList.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtIpInfo.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtRouterInfo.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtTraffic.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtUsage.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtClient.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtInterface.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtRouter.java [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/addon/addon.xml [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/i18n/asuswrt.properties [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/channels.xml [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/client.xml [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/interface.xml [new file with mode: 0644]
bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/router.xml [new file with mode: 0644]
bundles/pom.xml

index f821799122f80ca2e124c527827d183c8333aec0..eae74e331d6dc81198a9e49b3dface3788ac8dfa 100644 (file)
       <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>
diff --git a/bundles/org.openhab.binding.asuswrt/NOTICE b/bundles/org.openhab.binding.asuswrt/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -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 (file)
index 0000000..39dd664
--- /dev/null
@@ -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 (file)
index 0000000..2190968
--- /dev/null
@@ -0,0 +1,17 @@
+<?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>
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 (file)
index 0000000..7341628
--- /dev/null
@@ -0,0 +1,9 @@
+<?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>
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 (file)
index 0000000..252ced3
--- /dev/null
@@ -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<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();
+        }
+    }
+}
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 (file)
index 0000000..b52d3d0
--- /dev/null
@@ -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<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;
+    }
+}
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 (file)
index 0000000..f159881
--- /dev/null
@@ -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 <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;
+    }
+}
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 (file)
index 0000000..3e4c66a
--- /dev/null
@@ -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 <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;
+    }
+}
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 (file)
index 0000000..e8cd515
--- /dev/null
@@ -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 (file)
index 0000000..edcbdd2
--- /dev/null
@@ -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<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";
+}
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 (file)
index 0000000..f42798b
--- /dev/null
@@ -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<String> 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 (file)
index 0000000..78baab6
--- /dev/null
@@ -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 (file)
index 0000000..5775666
--- /dev/null
@@ -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 (file)
index 0000000..613797f
--- /dev/null
@@ -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 <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);
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtClientInfo.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtClientInfo.java
new file mode 100644 (file)
index 0000000..dfc3ab0
--- /dev/null
@@ -0,0 +1,235 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtClientList.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtClientList.java
new file mode 100644 (file)
index 0000000..be0a10f
--- /dev/null
@@ -0,0 +1,177 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtConfiguration.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtConfiguration.java
new file mode 100644 (file)
index 0000000..88d8010
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * 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;
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtCredentials.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtCredentials.java
new file mode 100644 (file)
index 0000000..30da6eb
--- /dev/null
@@ -0,0 +1,81 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtInterfaceList.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtInterfaceList.java
new file mode 100644 (file)
index 0000000..e24211b
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtIpInfo.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtIpInfo.java
new file mode 100644 (file)
index 0000000..c11d2a5
--- /dev/null
@@ -0,0 +1,124 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtRouterInfo.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtRouterInfo.java
new file mode 100644 (file)
index 0000000..7212a07
--- /dev/null
@@ -0,0 +1,154 @@
+/**
+ * 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);
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtTraffic.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtTraffic.java
new file mode 100644 (file)
index 0000000..5def6df
--- /dev/null
@@ -0,0 +1,191 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtUsage.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/structures/AsuswrtUsage.java
new file mode 100644 (file)
index 0000000..930afce
--- /dev/null
@@ -0,0 +1,114 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtClient.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtClient.java
new file mode 100644 (file)
index 0000000..70d6eb6
--- /dev/null
@@ -0,0 +1,196 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtInterface.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtInterface.java
new file mode 100644 (file)
index 0000000..77eddc8
--- /dev/null
@@ -0,0 +1,177 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtRouter.java b/bundles/org.openhab.binding.asuswrt/src/main/java/org/openhab/binding/asuswrt/internal/things/AsuswrtRouter.java
new file mode 100644 (file)
index 0000000..a9b0a52
--- /dev/null
@@ -0,0 +1,531 @@
+/**
+ * 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;
+    }
+}
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644 (file)
index 0000000..691ab88
--- /dev/null
@@ -0,0 +1,9 @@
+<?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>
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/i18n/asuswrt.properties b/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/i18n/asuswrt.properties
new file mode 100644 (file)
index 0000000..6d68088
--- /dev/null
@@ -0,0 +1,118 @@
+# 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
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/channels.xml
new file mode 100644 (file)
index 0000000..bb5b09a
--- /dev/null
@@ -0,0 +1,179 @@
+<?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>
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/client.xml b/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/client.xml
new file mode 100644 (file)
index 0000000..b492346
--- /dev/null
@@ -0,0 +1,53 @@
+<?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>
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/interface.xml b/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/interface.xml
new file mode 100644 (file)
index 0000000..42b65ad
--- /dev/null
@@ -0,0 +1,51 @@
+<?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>
diff --git a/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/router.xml b/bundles/org.openhab.binding.asuswrt/src/main/resources/OH-INF/thing/router.xml
new file mode 100644 (file)
index 0000000..0d7a1d6
--- /dev/null
@@ -0,0 +1,108 @@
+<?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>
index 4dd92d58950635cb2ebaa688d30dbf7b0b071c22..e5e5405c615d41670304d235d816574e195a75f0 100644 (file)
@@ -60,6 +60,7 @@
     <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>