diff --git a/bundles/org.openhab.binding.generacmobilelink/README.md b/bundles/org.openhab.binding.generacmobilelink/README.md
index 4b42a9517..a450b3987 100644
--- a/bundles/org.openhab.binding.generacmobilelink/README.md
+++ b/bundles/org.openhab.binding.generacmobilelink/README.md
@@ -36,22 +36,25 @@ The MobileLink account bridge must be added manually. Once added, generator thin
All channels are read-only.
-| channel | type | description |
-|-------------------------|----------------------|-------------------------------------------|
-| connected | Switch | Connected status |
-| greenLight | Switch | Green light state (typically auto mode) |
-| yellowLight | Switch | Yellow light state |
-| redLight | Switch | Red light state (typically off mode) |
-| blueLight | Switch | Blue light state (typically running mode) |
-| statusDate | DateTime | Status date (start of day) |
-| status | String | General status |
-| currentAlarmDescription | String | Current alarm description |
-| runHours | Number:Time | Number of run hours |
-| exerciseHours | Number:Time | Number of exercise hours |
-| fuelType | Number | Fuel type |
-| fuelLevel | Number:Dimensionless | Fuel level |
-| batteryVoltage | String | Battery voltage status |
-| serviceStatus | Switch | Service status |
+| Channel ID | Item Type | Description |
+|----------------------|-----------------------------|-----------------------------------|
+| heroImageUrl | String | Hero Image URL |
+| statusLabel | String | Status Label |
+| statusText | String | Status Text |
+| activationDate | DateTime | Activation Date |
+| deviceSsid | String | Device SSID |
+| status | Number | Status |
+| isConnected | Switch | Is Connected |
+| isConnecting | Switch | Is Connecting |
+| showWarning | Switch | Show Warning |
+| hasMaintenanceAlert | Switch | Has Maintenance Alert |
+| lastSeen | DateTime | Last Seen |
+| connectionTime | DateTime | Connection Time |
+| runHours | Number:Time | Number of Hours Run |
+| batteryVoltage | Number:ElectricPotential | Battery Voltage |
+| hoursOfProtection | Number:Time | Number of Hours of Protection |
+| signalStrength | Number:Dimensionless | Signal Strength |
+
## Full Example
@@ -66,27 +69,41 @@ Bridge generacmobilelink:account:main "MobileLink Account" [ userName="foo@bar.c
### Items
```java
-Switch GeneratorConnected "Connected [%s]" {channel="generacmobilelink:generator:main:123456:connected"}
-Switch GeneratorGreenLight "Green Light [%s]" {channel="generacmobilelink:generator:main:123456:greenLight"}
-Switch GeneratorYellowLight "Yellow Light [%s]" {channel="generacmobilelink:generator:main:123456:yellowLight"}
-Switch GeneratorBlueLight "Blue Light [%s]" {channel="generacmobilelink:generator:main:123456:blueLight"}
-Switch GeneratorRedLight "Red Light [%s]" {channel="generacmobilelink:generator:main:123456:redLight"}
-String GeneratorStatus "Status [%s]" {channel="generacmobilelink:generator:main:123456:status"}
-String GeneratorAlarm "Alarm [%s]" {channel="generacmobilelink:generator:main:123456:currentAlarmDescription"}
+String GeneratorHeroImageUrl "Hero Image URL [%s]" { channel="generacmobilelink:generator:main:123456:heroImageUrl" }
+String GeneratorStatusLabel "Status Label [%s]" { channel="generacmobilelink:generator:main:123456:statusLabel" }
+String GeneratorStatusText "Status Text [%s]" { channel="generacmobilelink:generator:main:123456:statusText" }
+DateTime GeneratorActivationDate "Activation Date [%s]" { channel="generacmobilelink:generator:main:123456:activationDate" }
+String GeneratorDeviceSsid "Device SSID [%s]" { channel="generacmobilelink:generator:main:123456:deviceSsid" }
+Number GeneratorStatus "Status [%d]" { channel="generacmobilelink:generator:main:123456:status" }
+Switch GeneratorIsConnected "Is Connected [%s]" { channel="generacmobilelink:generator:main:123456:isConnected" }
+Switch GeneratorIsConnecting "Is Connecting [%s]" { channel="generacmobilelink:generator:main:123456:isConnecting" }
+Switch GeneratorShowWarning "Show Warning [%s]" { channel="generacmobilelink:generator:main:123456:showWarning" }
+Switch GeneratorHasMaintenanceAlert "Has Maintenance Alert [%s]" { channel="generacmobilelink:generator:main:123456:hasMaintenanceAlert" }
+DateTime GeneratorLastSeen "Last Seen [%s]" { channel="generacmobilelink:generator:main:123456:lastSeen" }
+DateTime GeneratorConnectionTime "Connection Time [%s]" { channel="generacmobilelink:generator:main:123456:connectionTime" }
+Number:Time GeneratorRunHours "Number of Hours Run [%d]" { channel="generacmobilelink:generator:main:123456:runHours" }
+Number:ElectricPotential GeneratorBatteryVoltage "Battery Voltage [%d]v" { channel="generacmobilelink:generator:main:123456:batteryVoltage" }
+Number:Time GeneratorHoursOfProtection "Number of Hours of Protection [%d]" { channel="generacmobilelink:generator:main:123456:hoursOfProtection" }
+Number:Dimensionless GeneratorSignalStrength "Signal Strength [%d]" { channel="generacmobilelink:generator:main:123456:signalStrength" }
+
```
### Sitemap
```perl
-sitemap MobileLink label="Demo Sitemap" {
- Frame label="Generator" {
- Switch item=GeneratorConnected
- Switch item=GeneratorGreenLight
- Switch item=GeneratorYellowLight
- Switch item=GeneratorBlueLight
- Switch item=GeneratorRedLight
- Text item=GeneratorStatus
- Text item=GeneratorAlarm
- }
+sitemap generacmobilelink label="Generac MobileLink"
+{
+ Frame label="Generator Status" {
+ Text item=GeneratorStatus
+ Text item=GeneratorStatusLabel
+ Text item=GeneratorStatusText
+ }
+
+ Frame label="Generator Properties" {
+ Text item=GeneratorRunHours
+ Text item=GeneratorHoursOfProtection
+ Text item=GeneratorBatteryVoltage
+ Text item=GeneratorSignalStrength
+ }
}
```
diff --git a/bundles/org.openhab.binding.generacmobilelink/pom.xml b/bundles/org.openhab.binding.generacmobilelink/pom.xml
index 36c7c4722..30d73c0cc 100644
--- a/bundles/org.openhab.binding.generacmobilelink/pom.xml
+++ b/bundles/org.openhab.binding.generacmobilelink/pom.xml
@@ -14,4 +14,12 @@
openHAB Add-ons :: Bundles :: GeneracMobileLink Binding
+
+
+ org.jsoup
+ jsoup
+ 1.14.3
+ provided
+
+
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/feature/feature.xml b/bundles/org.openhab.binding.generacmobilelink/src/main/feature/feature.xml
index f569b0477..f06136a2a 100644
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/feature/feature.xml
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/feature/feature.xml
@@ -4,6 +4,7 @@
openhab-runtime-base
+ mvn:org.jsoup/jsoup/1.14.3
mvn:org.openhab.addons.bundles/org.openhab.binding.generacmobilelink/${project.version}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/GeneracMobileLinkBindingConstants.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/GeneracMobileLinkBindingConstants.java
index 86542ea1d..bf73de281 100644
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/GeneracMobileLinkBindingConstants.java
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/GeneracMobileLinkBindingConstants.java
@@ -23,7 +23,26 @@ import org.openhab.core.thing.ThingTypeUID;
*/
@NonNullByDefault
public class GeneracMobileLinkBindingConstants {
- private static final String BINDING_ID = "generacmobilelink";
+ public static final String BINDING_ID = "generacmobilelink";
public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
public static final ThingTypeUID THING_TYPE_GENERATOR = new ThingTypeUID(BINDING_ID, "generator");
+
+ public static final String PROPERTY_GENERATOR_ID = "generatorId";
+
+ public static final String CHANNEL_HERO_IMAGE_URL = "heroImageUrl";
+ public static final String CHANNEL_STATUS_LABEL = "statusLabel";
+ public static final String CHANNEL_STATUS_TEXT = "statusText";
+ public static final String CHANNEL_ACTIVATION_DATE = "activationDate";
+ public static final String CHANNEL_DEVICE_SSID = "deviceSsid";
+ public static final String CHANNEL_STATUS = "status";
+ public static final String CHANNEL_IS_CONNECTED = "isConnected";
+ public static final String CHANNEL_IS_CONNECTING = "isConnecting";
+ public static final String CHANNEL_SHOW_WARNING = "showWarning";
+ public static final String CHANNEL_HAS_MAINTENANCE_ALERT = "hasMaintenanceAlert";
+ public static final String CHANNEL_LAST_SEEN = "lastSeen";
+ public static final String CHANNEL_CONNECTION_TIME = "connectionTime";
+ public static final String CHANNEL_RUN_HOURS = "runHours";
+ public static final String CHANNEL_BATTERY_VOLTAGE = "batteryVoltage";
+ public static final String CHANNEL_HOURS_OF_PROTECTION = "hoursOfProtection";
+ public static final String CHANNEL_SIGNAL_STRENGH = "signalStrength";
}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusResponseDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/config/GeneracMobileLinkGeneratorConfiguration.java
similarity index 56%
rename from bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusResponseDTO.java
rename to bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/config/GeneracMobileLinkGeneratorConfiguration.java
index da7210880..a11a5b2c0 100644
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusResponseDTO.java
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/config/GeneracMobileLinkGeneratorConfiguration.java
@@ -10,16 +10,17 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.generacmobilelink.internal.dto;
+package org.openhab.binding.generacmobilelink.internal.config;
-import java.util.ArrayList;
+import org.eclipse.jdt.annotation.NonNullByDefault;
/**
- * {@link GeneratorStatusResponseDTO} response from the MobileLink API
+ * The {@link GeneracMobileLinkGeneratorConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Dan Cunningham - Initial contribution
*/
-@SuppressWarnings("serial")
-public class GeneratorStatusResponseDTO extends ArrayList {
+@NonNullByDefault
+public class GeneracMobileLinkGeneratorConfiguration {
+ public String generatorId = "";
}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/discovery/GeneracMobileLinkDiscoveryService.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/discovery/GeneracMobileLinkDiscoveryService.java
index f6b8fb7cf..78c9c5567 100644
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/discovery/GeneracMobileLinkDiscoveryService.java
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/discovery/GeneracMobileLinkDiscoveryService.java
@@ -12,21 +12,21 @@
*/
package org.openhab.binding.generacmobilelink.internal.discovery;
-import static org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants.THING_TYPE_GENERATOR;
+import static org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants;
-import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusDTO;
+import org.openhab.binding.generacmobilelink.internal.dto.Apparatus;
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.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
/**
- * The {@link GeneracMobileLinkDiscoveryService} is responsible for discovering generator things
+ * The {@link GeneracMobileLinkDiscoveryService} is responsible for discovering device things
*
* @author Dan Cunningham - Initial contribution
*/
@@ -52,13 +52,13 @@ public class GeneracMobileLinkDiscoveryService extends AbstractDiscoveryService
return false;
}
- public void generatorDiscovered(GeneratorStatusDTO generator, ThingUID bridgeUID) {
+ public void generatorDiscovered(Apparatus apparatus, ThingUID bridgeUID) {
DiscoveryResult result = DiscoveryResultBuilder
- .create(new ThingUID(GeneracMobileLinkBindingConstants.THING_TYPE_GENERATOR, bridgeUID,
- String.valueOf(generator.gensetID)))
- .withLabel("MobileLink Generator " + generator.generatorName)
- .withProperty("generatorId", String.valueOf(generator.gensetID))
- .withRepresentationProperty("generatorId").withBridge(bridgeUID).build();
+ .create(new ThingUID(THING_TYPE_GENERATOR, bridgeUID, String.valueOf(apparatus.apparatusId)))
+ .withLabel("MobileLink Generator " + apparatus.name)
+ .withProperty(Thing.PROPERTY_SERIAL_NUMBER, String.valueOf(apparatus.serialNumber))
+ .withProperty(PROPERTY_GENERATOR_ID, String.valueOf(apparatus.apparatusId))
+ .withRepresentationProperty(PROPERTY_GENERATOR_ID).withBridge(bridgeUID).build();
thingDiscovered(result);
}
}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/Account.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/Account.java
new file mode 100644
index 000000000..a3b520e3e
--- /dev/null
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/Account.java
@@ -0,0 +1,37 @@
+/**
+ * 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.generacmobilelink.internal.dto;
+
+/**
+ * The {@link Account} represents a Generac Account
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class Account {
+ public String userId;
+ public String firstName;
+ public String lastName;
+ public String[] emails;
+ public String[] phoneNumbers;
+ public String[] groups;
+ public MobileLinkSettings mobileLinkSettings;
+
+ public class MobileLinkSettings {
+ public DisplaySettings displaySettings;
+
+ public class DisplaySettings {
+ public String distanceUom;
+ public String temperatureUom;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/Apparatus.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/Apparatus.java
new file mode 100644
index 000000000..109b6b3b5
--- /dev/null
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/Apparatus.java
@@ -0,0 +1,79 @@
+/**
+ * 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.generacmobilelink.internal.dto;
+
+import java.util.List;
+
+/**
+ * The {@link Apparatus} represents a Generac Apparatus (Generator)
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class Apparatus {
+ public int apparatusId;
+ public String serialNumber;
+ public String name;
+ public int type;
+ public String localizedAddress;
+ public String materialDescription;
+ public String heroImageUrl;
+ public int apparatusStatus;
+ public boolean isConnected;
+ public boolean isConnecting;
+ public boolean showWarning;
+ public Weather weather;
+ public String preferredDealerName;
+ public String preferredDealerPhone;
+ public String preferredDealerEmail;
+ public boolean isDealerManaged;
+ public boolean isDealerUnmonitored;
+ public String modelNumber;
+ public String panelId;
+ public List properties;
+
+ public class Weather {
+ public Temperature temperature;
+ public int iconCode;
+
+ public class Temperature {
+ public double value;
+ public String unit;
+ public int unitType;
+ }
+ }
+
+ public class Property {
+ public String name;
+ public Value value;
+ public int type;
+
+ public class Value {
+ public int type;
+ public String status;
+ public boolean isLegacy;
+ public boolean isDunning;
+ public String deviceId;
+ public String deviceType;
+ public String signalStrength;
+ public String batteryLevel;
+ }
+ }
+
+ public class Device {
+ public String deviceId;
+ public String deviceType;
+ public String signalStrength;
+ public String batteryLevel;
+ public String status;
+ }
+}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ApparatusDetail.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ApparatusDetail.java
new file mode 100644
index 000000000..2d0f5cd41
--- /dev/null
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ApparatusDetail.java
@@ -0,0 +1,90 @@
+/**
+ * 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.generacmobilelink.internal.dto;
+
+import java.time.ZonedDateTime;
+
+/**
+ * The {@link ApparatusDetail} represents the details of a Generac Apparatus
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ApparatusDetail {
+ public int apparatusId;
+ public String name;
+ public String serialNumber;
+ public int apparatusClassification;
+ public String panelId;
+ public ZonedDateTime activationDate;
+ public String deviceType;
+ public String deviceSsid;
+ public String shortDeviceId;
+ public int apparatusStatus;
+ public String heroImageUrl;
+ public String statusLabel;
+ public String statusText;
+ public String eCodeLabel;
+ public Weather weather;
+ public boolean isConnected;
+ public boolean isConnecting;
+ public boolean showWarning;
+ public boolean hasMaintenanceAlert;
+ public ZonedDateTime lastSeen;
+ public String connectionTimestamp;
+ public Address address;
+ public Property[] properties;
+ public Subscription subscription;
+ public boolean enrolledInVpp;
+ public boolean hasActiveVppEvent;
+ public ProductInfo[] productInfo;
+ public boolean hasDisconnectedNotificationsOn;
+
+ public class Weather {
+ public Temperature temperature;
+ public int iconCode;
+
+ public class Temperature {
+ public double value;
+ public String unit;
+ public int unitType;
+ }
+ }
+
+ public class Address {
+ public String line1;
+ public String line2;
+ public String city;
+ public String region;
+ public String country;
+ public String postalCode;
+ }
+
+ public class Property {
+ public String name;
+ public String value;
+ public int type;
+ }
+
+ public class Subscription {
+ public int type;
+ public int status;
+ public boolean isLegacy;
+ public boolean isDunning;
+ }
+
+ public class ProductInfo {
+ public String name;
+ public String value;
+ public int type;
+ }
+}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ApparatusInfo.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ApparatusInfo.java
new file mode 100644
index 000000000..9d48a64f4
--- /dev/null
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ApparatusInfo.java
@@ -0,0 +1,39 @@
+/**
+ * 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.generacmobilelink.internal.dto;
+
+/**
+ * The {@link ApparatusInfo} represents the info of a Generac Apparatus
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class ApparatusInfo {
+ public int apparatusId;
+ public String apparatusName;
+ public String productType;
+ public String description;
+ public Property[] properties;
+ public Attribute[] attributes;
+
+ public class Property {
+ public String name;
+ public String value;
+ public int type;
+ }
+
+ public class Attribute {
+ public String name;
+ public String value;
+ public int type;
+ }
+}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusDTO.java
deleted file mode 100644
index 976be8762..000000000
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusDTO.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
- * 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.generacmobilelink.internal.dto;
-
-/**
- * {@link GeneratorStatusDTO} object from the MobileLink API
- *
- * @author Dan Cunningham - Initial contribution
- */
-public class GeneratorStatusDTO {
- public Integer gensetID;
- public String generatorDate;
- public String generatorName;
- public String generatorSerialNumber;
- public String generatorModel;
- public String generatorDescription;
- public String generatorMDN;
- public String generatorImei;
- public String generatorIccid;
- public String generatorTetherSerial;
- public Boolean connected;
- public Boolean greenLightLit;
- public Boolean yellowLightLit;
- public Boolean redLightLit;
- public Boolean blueLightLit;
- public String generatorStatus;
- public String generatorStatusDate;
- public String currentAlarmDescription;
- public Integer runHours;
- public Integer exerciseHours;
- public String batteryVoltage;
- public Integer fuelType;
- public Integer fuelLevel;
- public String generatorBrandImageURL;
- public Boolean generatorServiceStatus;
- public String signalStrength;
- public String deviceId;
- public Integer deviceTypeId;
- public String firmwareVersion;
- public String timezone;
- public String mACAddress;
- public String iPAddress;
- public String sSID;
-}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginRequestDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginRequestDTO.java
deleted file mode 100644
index fbba4afb3..000000000
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginRequestDTO.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * 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.generacmobilelink.internal.dto;
-
-/**
- * {@link LoginRequestDTO} request for the MobileLink API
- *
- * @author Dan Cunningham - Initial contribution
- */
-public class LoginRequestDTO {
- public LoginRequestDTO(String sharedKey, String userLogin, String userPassword) {
- super();
- this.sharedKey = sharedKey;
- this.userLogin = userLogin;
- this.userPassword = userPassword;
- }
-
- public String sharedKey;
- public String userLogin;
- public String userPassword;
-}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginResponseDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginResponseDTO.java
deleted file mode 100644
index 401e76c56..000000000
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginResponseDTO.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * 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.generacmobilelink.internal.dto;
-
-/**
- * {@link LoginResponseDTO} response from the MobileLink API
- *
- * @author Dan Cunningham - Initial contribution
- */
-public class LoginResponseDTO {
- public String authToken;
- public String pushChannelName;
-}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ErrorResponseDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/SelfAssertedResponse.java
similarity index 70%
rename from bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ErrorResponseDTO.java
rename to bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/SelfAssertedResponse.java
index c45de609d..6a895ad66 100644
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ErrorResponseDTO.java
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/SelfAssertedResponse.java
@@ -13,11 +13,12 @@
package org.openhab.binding.generacmobilelink.internal.dto;
/**
- * {@link ErrorResponseDTO} object from the MobileLink API
+ * The {@link SelfAssertedResponse} represents the SelfAssertedResponse object used in login
*
* @author Dan Cunningham - Initial contribution
*/
-public class ErrorResponseDTO {
- public Integer errorCode;
- public String errorMessage;
+public class SelfAssertedResponse {
+ public String status;
+ public String errorCode;
+ public String message;
}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/SignInConfig.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/SignInConfig.java
new file mode 100644
index 000000000..87a650016
--- /dev/null
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/SignInConfig.java
@@ -0,0 +1,51 @@
+/**
+ * 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.generacmobilelink.internal.dto;
+
+import java.util.Map;
+
+/**
+ * /**
+ * The {@link SignInConfig} represents the SignInConfig object used in login
+ *
+ * @author Dan Cunningham - Initial contribution
+ */
+public class SignInConfig {
+ public String remoteResource;
+ public int retryLimit;
+ public boolean trimSpacesInPassword;
+ public String api;
+ public String csrf;
+ public String transId;
+ public String pageViewId;
+ public boolean suppressElementCss;
+ public boolean isPageViewIdSentWithHeader;
+ public boolean allowAutoFocusOnPasswordField;
+ public int pageMode;
+ public Map config;
+ public Map hosts;
+ public Locale locale;
+ public XhrSettings xhrSettings;
+
+ public class Locale {
+ public String lang;
+ }
+
+ public class XhrSettings {
+ public boolean retryEnabled;
+ public int retryMaxAttempts;
+ public int retryDelay;
+ public int retryExponent;
+ public String[] retryOn;
+ }
+}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/factory/GeneracMobileLinkHandlerFactory.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/factory/GeneracMobileLinkHandlerFactory.java
index e14371827..2faf4e1f4 100644
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/factory/GeneracMobileLinkHandlerFactory.java
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/factory/GeneracMobileLinkHandlerFactory.java
@@ -21,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.generacmobilelink.internal.discovery.GeneracMobileLinkDiscoveryService;
import org.openhab.binding.generacmobilelink.internal.handler.GeneracMobileLinkAccountHandler;
import org.openhab.binding.generacmobilelink.internal.handler.GeneracMobileLinkGeneratorHandler;
@@ -51,11 +50,11 @@ public class GeneracMobileLinkHandlerFactory extends BaseThingHandlerFactory {
private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT,
THING_TYPE_GENERATOR);
private final Map> discoveryServiceRegs = new ConcurrentHashMap<>();
- private final HttpClient httpClient;
+ private final HttpClientFactory httpClientFactory;
@Activate
public GeneracMobileLinkHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
- this.httpClient = httpClientFactory.getCommonHttpClient();
+ this.httpClientFactory = httpClientFactory;
}
@Override
@@ -74,7 +73,7 @@ public class GeneracMobileLinkHandlerFactory extends BaseThingHandlerFactory {
if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
GeneracMobileLinkDiscoveryService discoveryService = new GeneracMobileLinkDiscoveryService();
GeneracMobileLinkAccountHandler accountHandler = new GeneracMobileLinkAccountHandler((Bridge) thing,
- httpClient, discoveryService);
+ httpClientFactory, discoveryService);
discoveryServiceRegs.put(accountHandler.getThing().getUID(), bundleContext
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
return accountHandler;
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkAccountHandler.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkAccountHandler.java
index 864c103d6..a18c0a218 100644
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkAccountHandler.java
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkAccountHandler.java
@@ -12,36 +12,42 @@
*/
package org.openhab.binding.generacmobilelink.internal.handler;
+import java.io.IOException;
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.api.ContentProvider;
+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.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.client.util.FormContentProvider;
+import org.eclipse.jetty.util.Fields;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
import org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants;
import org.openhab.binding.generacmobilelink.internal.config.GeneracMobileLinkAccountConfiguration;
+import org.openhab.binding.generacmobilelink.internal.config.GeneracMobileLinkGeneratorConfiguration;
import org.openhab.binding.generacmobilelink.internal.discovery.GeneracMobileLinkDiscoveryService;
-import org.openhab.binding.generacmobilelink.internal.dto.ErrorResponseDTO;
-import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusDTO;
-import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusResponseDTO;
-import org.openhab.binding.generacmobilelink.internal.dto.LoginRequestDTO;
-import org.openhab.binding.generacmobilelink.internal.dto.LoginResponseDTO;
+import org.openhab.binding.generacmobilelink.internal.dto.Apparatus;
+import org.openhab.binding.generacmobilelink.internal.dto.ApparatusDetail;
+import org.openhab.binding.generacmobilelink.internal.dto.SelfAssertedResponse;
+import org.openhab.binding.generacmobilelink.internal.dto.SignInConfig;
+import org.openhab.core.io.net.http.HttpClientFactory;
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.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
@@ -49,9 +55,10 @@ import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonSyntaxException;
/**
* The {@link GeneracMobileLinkAccountHandler} is responsible for connecting to the MobileLink cloud service and
@@ -61,191 +68,327 @@ import com.google.gson.GsonBuilder;
*/
@NonNullByDefault
public class GeneracMobileLinkAccountHandler extends BaseBridgeHandler {
- private static final String BASE_URL = "https://api.mobilelinkgen.com";
- private static final String SHARED_KEY = "GeneseeDepot13";
private final Logger logger = LoggerFactory.getLogger(GeneracMobileLinkAccountHandler.class);
- private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
- private @Nullable Future> pollFuture;
- private @Nullable String authToken;
- private @Nullable GeneratorStatusResponseDTO generators;
- private GeneracMobileLinkDiscoveryService discoveryService;
- private HttpClient httpClient;
- private int refreshIntervalSeconds = 60;
- public GeneracMobileLinkAccountHandler(Bridge bridge, HttpClient httpClient,
+ private static final String API_BASE = "https://app.mobilelinkgen.com/api";
+ private static final String LOGIN_BASE = "https://generacconnectivity.b2clogin.com/generacconnectivity.onmicrosoft.com/B2C_1A_MobileLink_SignIn";
+ private static final Pattern SETTINGS_PATTERN = Pattern.compile("^var SETTINGS = (.*);$", Pattern.MULTILINE);
+ private static final Gson GSON = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class,
+ (JsonDeserializer) (json, type, jsonDeserializationContext) -> {
+ return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString());
+ }).create();
+ private HttpClient httpClient;
+ private GeneracMobileLinkDiscoveryService discoveryService;
+ private Map apparatusesCache = new HashMap();
+ private int refreshIntervalSeconds = 60;
+ private boolean loggedIn;
+
+ private @Nullable Future> pollFuture;
+
+ public GeneracMobileLinkAccountHandler(Bridge bridge, HttpClientFactory httpClientFactory,
GeneracMobileLinkDiscoveryService discoveryService) {
super(bridge);
- this.httpClient = httpClient;
this.discoveryService = discoveryService;
+ httpClient = httpClientFactory.createHttpClient(GeneracMobileLinkBindingConstants.BINDING_ID);
+ httpClient.setFollowRedirects(true);
+ // We have to send a very large amount of cookies which exceeds the default buffer size
+ httpClient.setRequestBufferSize(16348);
+ try {
+ httpClient.start();
+ } catch (Exception e) {
+ throw new IllegalStateException("Error starting custom HttpClient", e);
+ }
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
- authToken = null;
- restartPoll();
+ stopOrRestartPoll(true);
}
@Override
public void dispose() {
- stopPoll();
+ stopOrRestartPoll(false);
+ try {
+ httpClient.stop();
+ } catch (Exception e) {
+ logger.debug("Could not stop HttpClient", e);
+ }
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
- updateGeneratorThings();
+ try {
+ updateGeneratorThings();
+ } catch (IOException | SessionExpiredException e) {
+ logger.debug("Could refresh things", e);
+ }
}
}
@Override
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
- GeneratorStatusResponseDTO generatorsLocal = generators;
- if (generatorsLocal != null) {
- Optional generatorOpt = generatorsLocal.stream()
- .filter(g -> String.valueOf(g.gensetID).equals(childThing.getUID().getId())).findFirst();
- if (generatorOpt.isPresent()) {
- ((GeneracMobileLinkGeneratorHandler) childHandler).updateGeneratorStatus(generatorOpt.get());
- }
+ logger.debug("childHandlerInitialized {}", childThing.getUID());
+ String id = childThing.getConfiguration().as(GeneracMobileLinkGeneratorConfiguration.class).generatorId;
+ Apparatus apparatus = apparatusesCache.get(id);
+ if (apparatus == null) {
+ logger.debug("No device for id {}", id);
+ return;
+ }
+ try {
+ updateGeneratorThing(childHandler, apparatus);
+ } catch (IOException | SessionExpiredException e) {
+ logger.debug("Could not initialize child", e);
}
}
- private void stopPoll() {
- Future> localPollFuture = pollFuture;
- if (localPollFuture != null) {
- localPollFuture.cancel(true);
+ private synchronized void stopOrRestartPoll(boolean restart) {
+ Future> pollFuture = this.pollFuture;
+ if (pollFuture != null) {
+ pollFuture.cancel(true);
+ this.pollFuture = null;
+ }
+ if (restart) {
+ this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 1, refreshIntervalSeconds, TimeUnit.SECONDS);
}
- }
-
- private void restartPoll() {
- stopPoll();
- pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshIntervalSeconds, TimeUnit.SECONDS);
}
private void poll() {
try {
- if (authToken == null) {
- logger.debug("Attempting Login");
+ if (!loggedIn) {
login();
}
- getStatuses(true);
- } catch (InterruptedException e) {
+ loggedIn = true;
+ updateGeneratorThings();
+ } catch (IOException e) {
+ logger.debug("Could not update devices", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "@text/thing.generacmobilelink.account.offline.communication-error.io-exception");
+ } catch (SessionExpiredException e) {
+ logger.debug("Session expired", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "@text/thing.generacmobilelink.account.offline.communication-error.session-expired");
+ loggedIn = false;
+ } catch (InvalidCredentialsException e) {
+ logger.debug("Credentials Invalid", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "@text/thing.generacmobilelink.account.offline.configuration-error.invalid-credentials");
+ loggedIn = false;
+ // we don't want to continue polling with bad credentials
+ stopOrRestartPoll(false);
}
}
- private synchronized void login() throws InterruptedException {
- GeneracMobileLinkAccountConfiguration config = getConfigAs(GeneracMobileLinkAccountConfiguration.class);
- refreshIntervalSeconds = config.refreshInterval;
- HTTPResult result = sendRequest(BASE_URL + "/Users/login", HttpMethod.POST, null,
- new StringContentProvider(
- gson.toJson(new LoginRequestDTO(SHARED_KEY, config.username, config.password))),
- "application/json");
- if (result.responseCode == HttpStatus.OK_200) {
- LoginResponseDTO loginResponse = gson.fromJson(result.content, LoginResponseDTO.class);
- if (loginResponse != null) {
- authToken = loginResponse.authToken;
- updateStatus(ThingStatus.ONLINE);
- }
- } else {
- handleErrorResponse(result);
- if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR) {
- // bad credentials, stop trying to login
- stopPoll();
- }
- }
- }
-
- private void getStatuses(boolean retry) throws InterruptedException {
- if (authToken == null) {
+ private void updateGeneratorThings() throws IOException, SessionExpiredException {
+ Apparatus[] apparatuses = getEndpoint(Apparatus[].class, "/v2/Apparatus/list");
+ if (apparatuses == null) {
+ logger.debug("Could not decode apparatuses response");
return;
}
- HTTPResult result = sendRequest(BASE_URL + "/Generator/GeneratorStatus", HttpMethod.GET, authToken, null, null);
- if (result.responseCode == HttpStatus.OK_200) {
- generators = gson.fromJson(result.content, GeneratorStatusResponseDTO.class);
- updateGeneratorThings();
- if (getThing().getStatus() != ThingStatus.ONLINE) {
- updateStatus(ThingStatus.ONLINE);
+ if (getThing().getStatus() != ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ }
+ for (Apparatus apparatus : apparatuses) {
+ if (apparatus.type != 0) {
+ logger.debug("Unknown apparatus type {} {}", apparatus.type, apparatus.name);
+ continue;
}
- } else {
- if (retry) {
- logger.debug("Retrying status request");
- getStatuses(false);
+
+ String id = String.valueOf(apparatus.apparatusId);
+ apparatusesCache.put(id, apparatus);
+
+ Optional thing = getThing().getThings().stream().filter(
+ t -> t.getConfiguration().as(GeneracMobileLinkGeneratorConfiguration.class).generatorId.equals(id))
+ .findFirst();
+ if (!thing.isPresent()) {
+ discoveryService.generatorDiscovered(apparatus, getThing().getUID());
} else {
- handleErrorResponse(result);
+ ThingHandler handler = thing.get().getHandler();
+ if (handler != null) {
+ updateGeneratorThing(handler, apparatus);
+ }
}
}
}
- private HTTPResult sendRequest(String url, HttpMethod method, @Nullable String token,
- @Nullable ContentProvider content, @Nullable String contentType) throws InterruptedException {
+ private void updateGeneratorThing(ThingHandler handler, Apparatus apparatus)
+ throws IOException, SessionExpiredException {
+ ApparatusDetail detail = getEndpoint(ApparatusDetail.class, "/v1/Apparatus/details/" + apparatus.apparatusId);
+ if (detail != null) {
+ ((GeneracMobileLinkGeneratorHandler) handler).updateGeneratorStatus(apparatus, detail);
+ } else {
+ logger.debug("Could not decode apparatuses detail response");
+ }
+ }
+
+ private @Nullable T getEndpoint(Class clazz, String endpoint) throws IOException, SessionExpiredException {
try {
- Request request = httpClient.newRequest(url).method(method).timeout(10, TimeUnit.SECONDS);
- if (token != null) {
- request = request.header("AuthToken", token);
+ ContentResponse response = httpClient.newRequest(API_BASE + endpoint).send();
+ if (response.getStatus() == 204) {
+ // no data
+ return null;
}
- if (content != null & contentType != null) {
- request = request.content(content, contentType);
+ if (response.getStatus() != 200) {
+ throw new SessionExpiredException("API returned status code: " + response.getStatus());
}
- logger.trace("Sending {} to {}", request.getMethod(), request.getURI());
- final CompletableFuture futureResult = new CompletableFuture<>();
- request.send(new BufferingResponseListener() {
- @NonNullByDefault({})
- @Override
- public void onComplete(Result result) {
- futureResult.complete(new HTTPResult(result.getResponse().getStatus(), getContentAsString()));
- }
- });
- HTTPResult result = futureResult.get();
- logger.trace("Response - status: {} content: {}", result.responseCode, result.content);
- return result;
- } catch (ExecutionException e) {
- return new HTTPResult(0, e.getMessage());
+ String data = response.getContentAsString();
+ logger.debug("getEndpoint {}", data);
+ return GSON.fromJson(data, clazz);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException(e);
+ } catch (TimeoutException | ExecutionException | JsonSyntaxException e) {
+ throw new IOException(e);
}
}
- private void handleErrorResponse(HTTPResult result) {
- switch (result.responseCode) {
- case HttpStatus.UNAUTHORIZED_401:
- // the server responds with a 500 error in some cases when credentials are not correct
- case HttpStatus.INTERNAL_SERVER_ERROR_500:
- // server returned a valid error response
- ErrorResponseDTO error = gson.fromJson(result.content, ErrorResponseDTO.class);
- if (error != null && error.errorCode > 0) {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
- "Unauthorized: " + result.content);
- authToken = null;
- break;
- }
- default:
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, result.content);
+ /**
+ * Attempts to login through a Microsoft Azure implicit grant oauth flow
+ *
+ * @throws IOException if there is a problem communicating or parsing the responses
+ * @throws InvalidCredentialsException If Azure rejects the login credentials.
+ */
+ private synchronized void login() throws IOException, InvalidCredentialsException {
+ logger.debug("Attempting login");
+ GeneracMobileLinkAccountConfiguration config = getConfigAs(GeneracMobileLinkAccountConfiguration.class);
+ refreshIntervalSeconds = config.refreshInterval;
+ try {
+ ContentResponse signInResponse = httpClient.newRequest(API_BASE + "/Auth/SignIn?email=" + config.username)
+ .send();
+
+ String responseData = signInResponse.getContentAsString();
+ logger.trace("response data: {}", responseData);
+
+ // If we are immediately returned a submit form, it means our cookies are still valid with the identity
+ // provider and we can just try and submit to the API service
+ if (submitPage(responseData)) {
+ return;
+ }
+
+ // Azure wants us to login again, look for the SETTINGS javascript in the page
+ Matcher matcher = SETTINGS_PATTERN.matcher(responseData);
+ if (!matcher.find()) {
+ throw new IOException("Could not find settings string");
+ }
+
+ String parseSettings = matcher.group(1);
+ logger.debug("parseSettings: {}", parseSettings);
+ SignInConfig signInConfig = GSON.fromJson(parseSettings, SignInConfig.class);
+
+ if (signInConfig == null) {
+ throw new IOException("Could not parse settings string");
+ }
+
+ Fields fields = new Fields();
+ fields.put("request_type", "RESPONSE");
+ fields.put("signInName", config.username);
+ fields.put("password", config.password);
+
+ Request selfAssertedRequest = httpClient.POST(LOGIN_BASE + "/SelfAsserted")
+ .header("X-Csrf-Token", signInConfig.csrf).param("tx", "StateProperties=" + signInConfig.transId)
+ .param("p", "B2C_1A_SignUpOrSigninOnline").content(new FormContentProvider(fields));
+
+ ContentResponse selfAssertedResponse = selfAssertedRequest.send();
+
+ logger.debug("selfAssertedRequest response {}", selfAssertedResponse.getStatus());
+
+ if (selfAssertedResponse.getStatus() != 200) {
+ throw new IOException("SelfAsserted: Bad response status: " + selfAssertedResponse.getStatus());
+ }
+
+ SelfAssertedResponse sa = GSON.fromJson(selfAssertedResponse.getContentAsString(),
+ SelfAssertedResponse.class);
+
+ if (sa == null) {
+ throw new IOException("SelfAsserted Could not parse response JSON");
+ }
+
+ if (!"200".equals(sa.status)) {
+ throw new InvalidCredentialsException("Invalid Credentials: " + sa.message);
+ }
+
+ Request confirmedRequest = httpClient.newRequest(LOGIN_BASE + "/api/CombinedSigninAndSignup/confirmed")
+ .param("csrf_token", signInConfig.csrf).param("tx", "StateProperties=" + signInConfig.transId)
+ .param("p", "B2C_1A_SignUpOrSigninOnline");
+
+ ContentResponse confirmedResponse = confirmedRequest.send();
+
+ if (confirmedResponse.getStatus() != 200) {
+ throw new IOException("CombinedSigninAndSignup bad response: " + confirmedResponse.getStatus());
+ }
+
+ String loginString = confirmedResponse.getContentAsString();
+ logger.trace("confirmedResponse: {}", loginString);
+ if (!submitPage(loginString)) {
+ throw new IOException("Error parsing HTML submit form");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException(e);
+ } catch (ExecutionException | TimeoutException | JsonSyntaxException e) {
+ throw new IOException(e);
}
}
- private void updateGeneratorThings() {
- GeneratorStatusResponseDTO generatorsLocal = generators;
- if (generatorsLocal != null) {
- generatorsLocal.forEach(generator -> {
- Thing thing = getThing().getThing(new ThingUID(GeneracMobileLinkBindingConstants.THING_TYPE_GENERATOR,
- getThing().getUID(), String.valueOf(generator.gensetID)));
- if (thing == null) {
- discoveryService.generatorDiscovered(generator, getThing().getUID());
- } else {
- ThingHandler handler = thing.getHandler();
- if (handler != null) {
- ((GeneracMobileLinkGeneratorHandler) handler).updateGeneratorStatus(generator);
- }
- }
- });
+ /**
+ * Attempts to submit a HTML form from Azure to the Generac API, returns false if the HTML does not match the
+ * required form
+ *
+ * @param loginString
+ * @return false if the HTML is not a form, true if submission is successful
+ * @throws ExecutionException
+ * @throws TimeoutException
+ * @throws InterruptedException
+ * @throws JsonSyntaxException
+ * @throws IOException
+ */
+ private boolean submitPage(String loginString)
+ throws ExecutionException, TimeoutException, InterruptedException, JsonSyntaxException, IOException {
+ Document loginPage = Jsoup.parse(loginString);
+ Element form = loginPage.select("form").first();
+ Element loginState = loginPage.select("input[name=state]").first();
+ Element loginCode = loginPage.select("input[name=code]").first();
+
+ if (form == null || loginState == null || loginCode == null) {
+ logger.debug("Could not load login page");
+ return false;
+ }
+
+ // url that the form will submit to
+ String action = form.attr("action");
+
+ Fields fields = new Fields();
+ fields.put("state", loginState.attr("value"));
+ fields.put("code", loginCode.attr("value"));
+
+ Request loginRequest = httpClient.POST(action).content(new FormContentProvider(fields));
+
+ ContentResponse loginResponse = loginRequest.send();
+ if (logger.isTraceEnabled()) {
+ logger.trace("login response {} {}", loginResponse.getStatus(), loginResponse.getContentAsString());
+ } else {
+ logger.debug("login response status {}", loginResponse.getStatus());
+ }
+ if (loginResponse.getStatus() != 200) {
+ throw new IOException("Bad api login resposne: " + loginResponse.getStatus());
+ }
+ return true;
+ }
+
+ private class InvalidCredentialsException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public InvalidCredentialsException(String message) {
+ super(message);
}
}
- public static class HTTPResult {
- public @Nullable String content;
- public final int responseCode;
+ private class SessionExpiredException extends Exception {
+ private static final long serialVersionUID = 1L;
- public HTTPResult(int responseCode, @Nullable String content) {
- this.responseCode = responseCode;
- this.content = content;
+ public SessionExpiredException(String message) {
+ super(message);
}
}
}
diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkGeneratorHandler.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkGeneratorHandler.java
index ea2bb3858..a02fbf91a 100644
--- a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkGeneratorHandler.java
+++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkGeneratorHandler.java
@@ -12,16 +12,18 @@
*/
package org.openhab.binding.generacmobilelink.internal.handler;
-import java.time.LocalDate;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
+import static org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants.*;
+import java.util.Arrays;
+
+import javax.measure.quantity.Dimensionless;
+import javax.measure.quantity.ElectricPotential;
import javax.measure.quantity.Time;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusDTO;
+import org.openhab.binding.generacmobilelink.internal.dto.Apparatus;
+import org.openhab.binding.generacmobilelink.internal.dto.ApparatusDetail;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
@@ -34,6 +36,7 @@ import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
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;
@@ -45,7 +48,9 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault
public class GeneracMobileLinkGeneratorHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(GeneracMobileLinkGeneratorHandler.class);
- private @Nullable GeneratorStatusDTO status;
+
+ private @Nullable Apparatus apparatus;
+ private @Nullable ApparatusDetail apparatusDetail;
public GeneracMobileLinkGeneratorHandler(Thing thing) {
super(thing);
@@ -63,37 +68,66 @@ public class GeneracMobileLinkGeneratorHandler extends BaseThingHandler {
updateStatus(ThingStatus.UNKNOWN);
}
- protected void updateGeneratorStatus(GeneratorStatusDTO status) {
- this.status = status;
+ protected void updateGeneratorStatus(Apparatus apparatus, ApparatusDetail apparatusDetail) {
+ this.apparatus = apparatus;
+ this.apparatusDetail = apparatusDetail;
updateStatus(ThingStatus.ONLINE);
updateState();
}
- protected void updateState() {
- final GeneratorStatusDTO localStatus = status;
- if (localStatus != null) {
- updateState("connected", OnOffType.from(localStatus.connected));
- updateState("greenLight", OnOffType.from(localStatus.greenLightLit));
- updateState("yellowLight", OnOffType.from(localStatus.yellowLightLit));
- updateState("redLight", OnOffType.from(localStatus.redLightLit));
- updateState("blueLight", OnOffType.from(localStatus.blueLightLit));
- try {
- // API returns a format like 12/20/2020
- updateState("statusDate",
- new DateTimeType(LocalDate
- .parse(localStatus.generatorStatusDate, DateTimeFormatter.ofPattern("MM/dd/yyyy"))
- .atStartOfDay(ZoneId.systemDefault())));
- } catch (IllegalArgumentException | DateTimeParseException e) {
- logger.debug("Could not parse statusDate", e);
- }
- updateState("status", new StringType(localStatus.generatorStatus));
- updateState("currentAlarmDescription", new StringType(localStatus.currentAlarmDescription));
- updateState("runHours", new QuantityType