mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 23:22:02 +01:00
[generacmobilelink] Major rewrite of the Generac MobileLink Binding (#14638)
* [generacmobilelink] Major rewrite of the Generac MobileLink Binding Signed-off-by: Dan Cunningham <dan@digitaldan.com>
This commit is contained in:
parent
09c394b928
commit
41701f518e
@ -36,22 +36,25 @@ The MobileLink account bridge must be added manually. Once added, generator thin
|
|||||||
|
|
||||||
All channels are read-only.
|
All channels are read-only.
|
||||||
|
|
||||||
| channel | type | description |
|
| Channel ID | Item Type | Description |
|
||||||
|-------------------------|----------------------|-------------------------------------------|
|
|----------------------|-----------------------------|-----------------------------------|
|
||||||
| connected | Switch | Connected status |
|
| heroImageUrl | String | Hero Image URL |
|
||||||
| greenLight | Switch | Green light state (typically auto mode) |
|
| statusLabel | String | Status Label |
|
||||||
| yellowLight | Switch | Yellow light state |
|
| statusText | String | Status Text |
|
||||||
| redLight | Switch | Red light state (typically off mode) |
|
| activationDate | DateTime | Activation Date |
|
||||||
| blueLight | Switch | Blue light state (typically running mode) |
|
| deviceSsid | String | Device SSID |
|
||||||
| statusDate | DateTime | Status date (start of day) |
|
| status | Number | Status |
|
||||||
| status | String | General status |
|
| isConnected | Switch | Is Connected |
|
||||||
| currentAlarmDescription | String | Current alarm description |
|
| isConnecting | Switch | Is Connecting |
|
||||||
| runHours | Number:Time | Number of run hours |
|
| showWarning | Switch | Show Warning |
|
||||||
| exerciseHours | Number:Time | Number of exercise hours |
|
| hasMaintenanceAlert | Switch | Has Maintenance Alert |
|
||||||
| fuelType | Number | Fuel type |
|
| lastSeen | DateTime | Last Seen |
|
||||||
| fuelLevel | Number:Dimensionless | Fuel level |
|
| connectionTime | DateTime | Connection Time |
|
||||||
| batteryVoltage | String | Battery voltage status |
|
| runHours | Number:Time | Number of Hours Run |
|
||||||
| serviceStatus | Switch | Service status |
|
| batteryVoltage | Number:ElectricPotential | Battery Voltage |
|
||||||
|
| hoursOfProtection | Number:Time | Number of Hours of Protection |
|
||||||
|
| signalStrength | Number:Dimensionless | Signal Strength |
|
||||||
|
|
||||||
|
|
||||||
## Full Example
|
## Full Example
|
||||||
|
|
||||||
@ -66,27 +69,41 @@ Bridge generacmobilelink:account:main "MobileLink Account" [ userName="foo@bar.c
|
|||||||
### Items
|
### Items
|
||||||
|
|
||||||
```java
|
```java
|
||||||
Switch GeneratorConnected "Connected [%s]" {channel="generacmobilelink:generator:main:123456:connected"}
|
String GeneratorHeroImageUrl "Hero Image URL [%s]" { channel="generacmobilelink:generator:main:123456:heroImageUrl" }
|
||||||
Switch GeneratorGreenLight "Green Light [%s]" {channel="generacmobilelink:generator:main:123456:greenLight"}
|
String GeneratorStatusLabel "Status Label [%s]" { channel="generacmobilelink:generator:main:123456:statusLabel" }
|
||||||
Switch GeneratorYellowLight "Yellow Light [%s]" {channel="generacmobilelink:generator:main:123456:yellowLight"}
|
String GeneratorStatusText "Status Text [%s]" { channel="generacmobilelink:generator:main:123456:statusText" }
|
||||||
Switch GeneratorBlueLight "Blue Light [%s]" {channel="generacmobilelink:generator:main:123456:blueLight"}
|
DateTime GeneratorActivationDate "Activation Date [%s]" { channel="generacmobilelink:generator:main:123456:activationDate" }
|
||||||
Switch GeneratorRedLight "Red Light [%s]" {channel="generacmobilelink:generator:main:123456:redLight"}
|
String GeneratorDeviceSsid "Device SSID [%s]" { channel="generacmobilelink:generator:main:123456:deviceSsid" }
|
||||||
String GeneratorStatus "Status [%s]" {channel="generacmobilelink:generator:main:123456:status"}
|
Number GeneratorStatus "Status [%d]" { channel="generacmobilelink:generator:main:123456:status" }
|
||||||
String GeneratorAlarm "Alarm [%s]" {channel="generacmobilelink:generator:main:123456:currentAlarmDescription"}
|
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
|
### Sitemap
|
||||||
|
|
||||||
```perl
|
```perl
|
||||||
sitemap MobileLink label="Demo Sitemap" {
|
sitemap generacmobilelink label="Generac MobileLink"
|
||||||
Frame label="Generator" {
|
{
|
||||||
Switch item=GeneratorConnected
|
Frame label="Generator Status" {
|
||||||
Switch item=GeneratorGreenLight
|
Text item=GeneratorStatus
|
||||||
Switch item=GeneratorYellowLight
|
Text item=GeneratorStatusLabel
|
||||||
Switch item=GeneratorBlueLight
|
Text item=GeneratorStatusText
|
||||||
Switch item=GeneratorRedLight
|
}
|
||||||
Text item=GeneratorStatus
|
|
||||||
Text item=GeneratorAlarm
|
Frame label="Generator Properties" {
|
||||||
}
|
Text item=GeneratorRunHours
|
||||||
|
Text item=GeneratorHoursOfProtection
|
||||||
|
Text item=GeneratorBatteryVoltage
|
||||||
|
Text item=GeneratorSignalStrength
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -14,4 +14,12 @@
|
|||||||
|
|
||||||
<name>openHAB Add-ons :: Bundles :: GeneracMobileLink Binding</name>
|
<name>openHAB Add-ons :: Bundles :: GeneracMobileLink Binding</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.14.3</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
<feature name="openhab-binding-generacmobilelink" description="Generac MobileLink Binding" version="${project.version}">
|
<feature name="openhab-binding-generacmobilelink" description="Generac MobileLink Binding" version="${project.version}">
|
||||||
<feature>openhab-runtime-base</feature>
|
<feature>openhab-runtime-base</feature>
|
||||||
|
<bundle dependency="true">mvn:org.jsoup/jsoup/1.14.3</bundle>
|
||||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.generacmobilelink/${project.version}</bundle>
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.generacmobilelink/${project.version}</bundle>
|
||||||
</feature>
|
</feature>
|
||||||
</features>
|
</features>
|
||||||
|
@ -23,7 +23,26 @@ import org.openhab.core.thing.ThingTypeUID;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class GeneracMobileLinkBindingConstants {
|
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_ACCOUNT = new ThingTypeUID(BINDING_ID, "account");
|
||||||
public static final ThingTypeUID THING_TYPE_GENERATOR = new ThingTypeUID(BINDING_ID, "generator");
|
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";
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,17 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* 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
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@NonNullByDefault
|
||||||
public class GeneratorStatusResponseDTO extends ArrayList<GeneratorStatusDTO> {
|
public class GeneracMobileLinkGeneratorConfiguration {
|
||||||
|
|
||||||
|
public String generatorId = "";
|
||||||
}
|
}
|
@ -12,21 +12,21 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.generacmobilelink.internal.discovery;
|
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 java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants;
|
import org.openhab.binding.generacmobilelink.internal.dto.Apparatus;
|
||||||
import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusDTO;
|
|
||||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
import org.openhab.core.thing.ThingUID;
|
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
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
@ -52,13 +52,13 @@ public class GeneracMobileLinkDiscoveryService extends AbstractDiscoveryService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void generatorDiscovered(GeneratorStatusDTO generator, ThingUID bridgeUID) {
|
public void generatorDiscovered(Apparatus apparatus, ThingUID bridgeUID) {
|
||||||
DiscoveryResult result = DiscoveryResultBuilder
|
DiscoveryResult result = DiscoveryResultBuilder
|
||||||
.create(new ThingUID(GeneracMobileLinkBindingConstants.THING_TYPE_GENERATOR, bridgeUID,
|
.create(new ThingUID(THING_TYPE_GENERATOR, bridgeUID, String.valueOf(apparatus.apparatusId)))
|
||||||
String.valueOf(generator.gensetID)))
|
.withLabel("MobileLink Generator " + apparatus.name)
|
||||||
.withLabel("MobileLink Generator " + generator.generatorName)
|
.withProperty(Thing.PROPERTY_SERIAL_NUMBER, String.valueOf(apparatus.serialNumber))
|
||||||
.withProperty("generatorId", String.valueOf(generator.gensetID))
|
.withProperty(PROPERTY_GENERATOR_ID, String.valueOf(apparatus.apparatusId))
|
||||||
.withRepresentationProperty("generatorId").withBridge(bridgeUID).build();
|
.withRepresentationProperty(PROPERTY_GENERATOR_ID).withBridge(bridgeUID).build();
|
||||||
thingDiscovered(result);
|
thingDiscovered(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<Property> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -13,11 +13,12 @@
|
|||||||
package org.openhab.binding.generacmobilelink.internal.dto;
|
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
|
* @author Dan Cunningham - Initial contribution
|
||||||
*/
|
*/
|
||||||
public class ErrorResponseDTO {
|
public class SelfAssertedResponse {
|
||||||
public Integer errorCode;
|
public String status;
|
||||||
public String errorMessage;
|
public String errorCode;
|
||||||
|
public String message;
|
||||||
}
|
}
|
@ -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<String, String> config;
|
||||||
|
public Map<String, String> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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.discovery.GeneracMobileLinkDiscoveryService;
|
||||||
import org.openhab.binding.generacmobilelink.internal.handler.GeneracMobileLinkAccountHandler;
|
import org.openhab.binding.generacmobilelink.internal.handler.GeneracMobileLinkAccountHandler;
|
||||||
import org.openhab.binding.generacmobilelink.internal.handler.GeneracMobileLinkGeneratorHandler;
|
import org.openhab.binding.generacmobilelink.internal.handler.GeneracMobileLinkGeneratorHandler;
|
||||||
@ -51,11 +50,11 @@ public class GeneracMobileLinkHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT,
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT,
|
||||||
THING_TYPE_GENERATOR);
|
THING_TYPE_GENERATOR);
|
||||||
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new ConcurrentHashMap<>();
|
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new ConcurrentHashMap<>();
|
||||||
private final HttpClient httpClient;
|
private final HttpClientFactory httpClientFactory;
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public GeneracMobileLinkHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
|
public GeneracMobileLinkHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
|
||||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
this.httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -74,7 +73,7 @@ public class GeneracMobileLinkHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
|
if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) {
|
||||||
GeneracMobileLinkDiscoveryService discoveryService = new GeneracMobileLinkDiscoveryService();
|
GeneracMobileLinkDiscoveryService discoveryService = new GeneracMobileLinkDiscoveryService();
|
||||||
GeneracMobileLinkAccountHandler accountHandler = new GeneracMobileLinkAccountHandler((Bridge) thing,
|
GeneracMobileLinkAccountHandler accountHandler = new GeneracMobileLinkAccountHandler((Bridge) thing,
|
||||||
httpClient, discoveryService);
|
httpClientFactory, discoveryService);
|
||||||
discoveryServiceRegs.put(accountHandler.getThing().getUID(), bundleContext
|
discoveryServiceRegs.put(accountHandler.getThing().getUID(), bundleContext
|
||||||
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||||
return accountHandler;
|
return accountHandler;
|
||||||
|
@ -12,36 +12,42 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.generacmobilelink.internal.handler;
|
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.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
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.Request;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.util.FormContentProvider;
|
||||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
import org.eclipse.jetty.util.Fields;
|
||||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
import org.jsoup.Jsoup;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.jsoup.nodes.Document;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.jsoup.nodes.Element;
|
||||||
import org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants;
|
import org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants;
|
||||||
import org.openhab.binding.generacmobilelink.internal.config.GeneracMobileLinkAccountConfiguration;
|
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.discovery.GeneracMobileLinkDiscoveryService;
|
||||||
import org.openhab.binding.generacmobilelink.internal.dto.ErrorResponseDTO;
|
import org.openhab.binding.generacmobilelink.internal.dto.Apparatus;
|
||||||
import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusDTO;
|
import org.openhab.binding.generacmobilelink.internal.dto.ApparatusDetail;
|
||||||
import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusResponseDTO;
|
import org.openhab.binding.generacmobilelink.internal.dto.SelfAssertedResponse;
|
||||||
import org.openhab.binding.generacmobilelink.internal.dto.LoginRequestDTO;
|
import org.openhab.binding.generacmobilelink.internal.dto.SignInConfig;
|
||||||
import org.openhab.binding.generacmobilelink.internal.dto.LoginResponseDTO;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
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.BaseBridgeHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandler;
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
@ -49,9 +55,10 @@ import org.openhab.core.types.RefreshType;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.google.gson.FieldNamingPolicy;
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
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
|
* The {@link GeneracMobileLinkAccountHandler} is responsible for connecting to the MobileLink cloud service and
|
||||||
@ -61,191 +68,327 @@ import com.google.gson.GsonBuilder;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class GeneracMobileLinkAccountHandler extends BaseBridgeHandler {
|
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 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<ZonedDateTime>) (json, type, jsonDeserializationContext) -> {
|
||||||
|
return ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString());
|
||||||
|
}).create();
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private GeneracMobileLinkDiscoveryService discoveryService;
|
||||||
|
private Map<String, Apparatus> apparatusesCache = new HashMap<String, Apparatus>();
|
||||||
|
private int refreshIntervalSeconds = 60;
|
||||||
|
private boolean loggedIn;
|
||||||
|
|
||||||
|
private @Nullable Future<?> pollFuture;
|
||||||
|
|
||||||
|
public GeneracMobileLinkAccountHandler(Bridge bridge, HttpClientFactory httpClientFactory,
|
||||||
GeneracMobileLinkDiscoveryService discoveryService) {
|
GeneracMobileLinkDiscoveryService discoveryService) {
|
||||||
super(bridge);
|
super(bridge);
|
||||||
this.httpClient = httpClient;
|
|
||||||
this.discoveryService = discoveryService;
|
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
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
updateStatus(ThingStatus.UNKNOWN);
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
authToken = null;
|
stopOrRestartPoll(true);
|
||||||
restartPoll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
stopPoll();
|
stopOrRestartPoll(false);
|
||||||
|
try {
|
||||||
|
httpClient.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("Could not stop HttpClient", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
if (command instanceof RefreshType) {
|
if (command instanceof RefreshType) {
|
||||||
updateGeneratorThings();
|
try {
|
||||||
|
updateGeneratorThings();
|
||||||
|
} catch (IOException | SessionExpiredException e) {
|
||||||
|
logger.debug("Could refresh things", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||||
GeneratorStatusResponseDTO generatorsLocal = generators;
|
logger.debug("childHandlerInitialized {}", childThing.getUID());
|
||||||
if (generatorsLocal != null) {
|
String id = childThing.getConfiguration().as(GeneracMobileLinkGeneratorConfiguration.class).generatorId;
|
||||||
Optional<GeneratorStatusDTO> generatorOpt = generatorsLocal.stream()
|
Apparatus apparatus = apparatusesCache.get(id);
|
||||||
.filter(g -> String.valueOf(g.gensetID).equals(childThing.getUID().getId())).findFirst();
|
if (apparatus == null) {
|
||||||
if (generatorOpt.isPresent()) {
|
logger.debug("No device for id {}", id);
|
||||||
((GeneracMobileLinkGeneratorHandler) childHandler).updateGeneratorStatus(generatorOpt.get());
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
updateGeneratorThing(childHandler, apparatus);
|
||||||
|
} catch (IOException | SessionExpiredException e) {
|
||||||
|
logger.debug("Could not initialize child", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopPoll() {
|
private synchronized void stopOrRestartPoll(boolean restart) {
|
||||||
Future<?> localPollFuture = pollFuture;
|
Future<?> pollFuture = this.pollFuture;
|
||||||
if (localPollFuture != null) {
|
if (pollFuture != null) {
|
||||||
localPollFuture.cancel(true);
|
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() {
|
private void poll() {
|
||||||
try {
|
try {
|
||||||
if (authToken == null) {
|
if (!loggedIn) {
|
||||||
logger.debug("Attempting Login");
|
|
||||||
login();
|
login();
|
||||||
}
|
}
|
||||||
getStatuses(true);
|
loggedIn = true;
|
||||||
} catch (InterruptedException e) {
|
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 {
|
private void updateGeneratorThings() throws IOException, SessionExpiredException {
|
||||||
GeneracMobileLinkAccountConfiguration config = getConfigAs(GeneracMobileLinkAccountConfiguration.class);
|
Apparatus[] apparatuses = getEndpoint(Apparatus[].class, "/v2/Apparatus/list");
|
||||||
refreshIntervalSeconds = config.refreshInterval;
|
if (apparatuses == null) {
|
||||||
HTTPResult result = sendRequest(BASE_URL + "/Users/login", HttpMethod.POST, null,
|
logger.debug("Could not decode apparatuses response");
|
||||||
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) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
HTTPResult result = sendRequest(BASE_URL + "/Generator/GeneratorStatus", HttpMethod.GET, authToken, null, null);
|
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
if (result.responseCode == HttpStatus.OK_200) {
|
updateStatus(ThingStatus.ONLINE);
|
||||||
generators = gson.fromJson(result.content, GeneratorStatusResponseDTO.class);
|
}
|
||||||
updateGeneratorThings();
|
for (Apparatus apparatus : apparatuses) {
|
||||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
if (apparatus.type != 0) {
|
||||||
updateStatus(ThingStatus.ONLINE);
|
logger.debug("Unknown apparatus type {} {}", apparatus.type, apparatus.name);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (retry) {
|
String id = String.valueOf(apparatus.apparatusId);
|
||||||
logger.debug("Retrying status request");
|
apparatusesCache.put(id, apparatus);
|
||||||
getStatuses(false);
|
|
||||||
|
Optional<Thing> thing = getThing().getThings().stream().filter(
|
||||||
|
t -> t.getConfiguration().as(GeneracMobileLinkGeneratorConfiguration.class).generatorId.equals(id))
|
||||||
|
.findFirst();
|
||||||
|
if (!thing.isPresent()) {
|
||||||
|
discoveryService.generatorDiscovered(apparatus, getThing().getUID());
|
||||||
} else {
|
} else {
|
||||||
handleErrorResponse(result);
|
ThingHandler handler = thing.get().getHandler();
|
||||||
|
if (handler != null) {
|
||||||
|
updateGeneratorThing(handler, apparatus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private HTTPResult sendRequest(String url, HttpMethod method, @Nullable String token,
|
private void updateGeneratorThing(ThingHandler handler, Apparatus apparatus)
|
||||||
@Nullable ContentProvider content, @Nullable String contentType) throws InterruptedException {
|
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> T getEndpoint(Class<T> clazz, String endpoint) throws IOException, SessionExpiredException {
|
||||||
try {
|
try {
|
||||||
Request request = httpClient.newRequest(url).method(method).timeout(10, TimeUnit.SECONDS);
|
ContentResponse response = httpClient.newRequest(API_BASE + endpoint).send();
|
||||||
if (token != null) {
|
if (response.getStatus() == 204) {
|
||||||
request = request.header("AuthToken", token);
|
// no data
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
if (content != null & contentType != null) {
|
if (response.getStatus() != 200) {
|
||||||
request = request.content(content, contentType);
|
throw new SessionExpiredException("API returned status code: " + response.getStatus());
|
||||||
}
|
}
|
||||||
logger.trace("Sending {} to {}", request.getMethod(), request.getURI());
|
String data = response.getContentAsString();
|
||||||
final CompletableFuture<HTTPResult> futureResult = new CompletableFuture<>();
|
logger.debug("getEndpoint {}", data);
|
||||||
request.send(new BufferingResponseListener() {
|
return GSON.fromJson(data, clazz);
|
||||||
@NonNullByDefault({})
|
} catch (InterruptedException e) {
|
||||||
@Override
|
Thread.currentThread().interrupt();
|
||||||
public void onComplete(Result result) {
|
throw new IOException(e);
|
||||||
futureResult.complete(new HTTPResult(result.getResponse().getStatus(), getContentAsString()));
|
} catch (TimeoutException | ExecutionException | JsonSyntaxException e) {
|
||||||
}
|
throw new IOException(e);
|
||||||
});
|
|
||||||
HTTPResult result = futureResult.get();
|
|
||||||
logger.trace("Response - status: {} content: {}", result.responseCode, result.content);
|
|
||||||
return result;
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
return new HTTPResult(0, e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleErrorResponse(HTTPResult result) {
|
/**
|
||||||
switch (result.responseCode) {
|
* Attempts to login through a Microsoft Azure implicit grant oauth flow
|
||||||
case HttpStatus.UNAUTHORIZED_401:
|
*
|
||||||
// the server responds with a 500 error in some cases when credentials are not correct
|
* @throws IOException if there is a problem communicating or parsing the responses
|
||||||
case HttpStatus.INTERNAL_SERVER_ERROR_500:
|
* @throws InvalidCredentialsException If Azure rejects the login credentials.
|
||||||
// server returned a valid error response
|
*/
|
||||||
ErrorResponseDTO error = gson.fromJson(result.content, ErrorResponseDTO.class);
|
private synchronized void login() throws IOException, InvalidCredentialsException {
|
||||||
if (error != null && error.errorCode > 0) {
|
logger.debug("Attempting login");
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
GeneracMobileLinkAccountConfiguration config = getConfigAs(GeneracMobileLinkAccountConfiguration.class);
|
||||||
"Unauthorized: " + result.content);
|
refreshIntervalSeconds = config.refreshInterval;
|
||||||
authToken = null;
|
try {
|
||||||
break;
|
ContentResponse signInResponse = httpClient.newRequest(API_BASE + "/Auth/SignIn?email=" + config.username)
|
||||||
}
|
.send();
|
||||||
default:
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, result.content);
|
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;
|
* Attempts to submit a HTML form from Azure to the Generac API, returns false if the HTML does not match the
|
||||||
if (generatorsLocal != null) {
|
* required form
|
||||||
generatorsLocal.forEach(generator -> {
|
*
|
||||||
Thing thing = getThing().getThing(new ThingUID(GeneracMobileLinkBindingConstants.THING_TYPE_GENERATOR,
|
* @param loginString
|
||||||
getThing().getUID(), String.valueOf(generator.gensetID)));
|
* @return false if the HTML is not a form, true if submission is successful
|
||||||
if (thing == null) {
|
* @throws ExecutionException
|
||||||
discoveryService.generatorDiscovered(generator, getThing().getUID());
|
* @throws TimeoutException
|
||||||
} else {
|
* @throws InterruptedException
|
||||||
ThingHandler handler = thing.getHandler();
|
* @throws JsonSyntaxException
|
||||||
if (handler != null) {
|
* @throws IOException
|
||||||
((GeneracMobileLinkGeneratorHandler) handler).updateGeneratorStatus(generator);
|
*/
|
||||||
}
|
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 {
|
private class SessionExpiredException extends Exception {
|
||||||
public @Nullable String content;
|
private static final long serialVersionUID = 1L;
|
||||||
public final int responseCode;
|
|
||||||
|
|
||||||
public HTTPResult(int responseCode, @Nullable String content) {
|
public SessionExpiredException(String message) {
|
||||||
this.responseCode = responseCode;
|
super(message);
|
||||||
this.content = content;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,16 +12,18 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.generacmobilelink.internal.handler;
|
package org.openhab.binding.generacmobilelink.internal.handler;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import static org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants.*;
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.DateTimeParseException;
|
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.measure.quantity.Dimensionless;
|
||||||
|
import javax.measure.quantity.ElectricPotential;
|
||||||
import javax.measure.quantity.Time;
|
import javax.measure.quantity.Time;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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.DateTimeType;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.OnOffType;
|
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.thing.binding.BaseThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -45,7 +48,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class GeneracMobileLinkGeneratorHandler extends BaseThingHandler {
|
public class GeneracMobileLinkGeneratorHandler extends BaseThingHandler {
|
||||||
private final Logger logger = LoggerFactory.getLogger(GeneracMobileLinkGeneratorHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(GeneracMobileLinkGeneratorHandler.class);
|
||||||
private @Nullable GeneratorStatusDTO status;
|
|
||||||
|
private @Nullable Apparatus apparatus;
|
||||||
|
private @Nullable ApparatusDetail apparatusDetail;
|
||||||
|
|
||||||
public GeneracMobileLinkGeneratorHandler(Thing thing) {
|
public GeneracMobileLinkGeneratorHandler(Thing thing) {
|
||||||
super(thing);
|
super(thing);
|
||||||
@ -63,37 +68,66 @@ public class GeneracMobileLinkGeneratorHandler extends BaseThingHandler {
|
|||||||
updateStatus(ThingStatus.UNKNOWN);
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateGeneratorStatus(GeneratorStatusDTO status) {
|
protected void updateGeneratorStatus(Apparatus apparatus, ApparatusDetail apparatusDetail) {
|
||||||
this.status = status;
|
this.apparatus = apparatus;
|
||||||
|
this.apparatusDetail = apparatusDetail;
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
updateState();
|
updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateState() {
|
private void updateState() {
|
||||||
final GeneratorStatusDTO localStatus = status;
|
Apparatus apparatus = this.apparatus;
|
||||||
if (localStatus != null) {
|
ApparatusDetail apparatusDetail = this.apparatusDetail;
|
||||||
updateState("connected", OnOffType.from(localStatus.connected));
|
if (apparatus == null || apparatusDetail == null) {
|
||||||
updateState("greenLight", OnOffType.from(localStatus.greenLightLit));
|
return;
|
||||||
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<Time>(localStatus.runHours, Units.HOUR));
|
|
||||||
updateState("exerciseHours", new QuantityType<Time>(localStatus.exerciseHours, Units.HOUR));
|
|
||||||
updateState("fuelType", new DecimalType(localStatus.fuelType));
|
|
||||||
updateState("fuelLevel", QuantityType.valueOf(localStatus.fuelLevel, Units.PERCENT));
|
|
||||||
updateState("batteryVoltage", new StringType(localStatus.batteryVoltage));
|
|
||||||
updateState("serviceStatus", OnOffType.from(localStatus.generatorServiceStatus));
|
|
||||||
}
|
}
|
||||||
|
updateState(CHANNEL_HERO_IMAGE_URL, new StringType(apparatusDetail.heroImageUrl));
|
||||||
|
updateState(CHANNEL_STATUS_LABEL, new StringType(apparatusDetail.statusLabel));
|
||||||
|
updateState(CHANNEL_STATUS_TEXT, new StringType(apparatusDetail.statusText));
|
||||||
|
updateState(CHANNEL_ACTIVATION_DATE, new DateTimeType(apparatusDetail.activationDate));
|
||||||
|
updateState(CHANNEL_DEVICE_SSID, new StringType(apparatusDetail.deviceSsid));
|
||||||
|
updateState(CHANNEL_STATUS, new DecimalType(apparatusDetail.apparatusStatus));
|
||||||
|
updateState(CHANNEL_IS_CONNECTED, OnOffType.from(apparatusDetail.isConnected));
|
||||||
|
updateState(CHANNEL_IS_CONNECTING, OnOffType.from(apparatusDetail.isConnecting));
|
||||||
|
updateState(CHANNEL_SHOW_WARNING, OnOffType.from(apparatusDetail.showWarning));
|
||||||
|
updateState(CHANNEL_HAS_MAINTENANCE_ALERT, OnOffType.from(apparatusDetail.hasMaintenanceAlert));
|
||||||
|
updateState(CHANNEL_LAST_SEEN, new DateTimeType(apparatusDetail.lastSeen));
|
||||||
|
updateState(CHANNEL_CONNECTION_TIME, new DateTimeType(apparatusDetail.connectionTimestamp));
|
||||||
|
Arrays.stream(apparatusDetail.properties).filter(p -> p.type == 70).findFirst().ifPresent(p -> {
|
||||||
|
try {
|
||||||
|
updateState(CHANNEL_RUN_HOURS, new QuantityType<Time>(Integer.parseInt(p.value), Units.HOUR));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.debug("Could not parse runHours {}", p.value);
|
||||||
|
updateState(CHANNEL_RUN_HOURS, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Arrays.stream(apparatusDetail.properties).filter(p -> p.type == 69).findFirst().ifPresent(p -> {
|
||||||
|
try {
|
||||||
|
updateState(CHANNEL_BATTERY_VOLTAGE,
|
||||||
|
new QuantityType<ElectricPotential>(Float.parseFloat(p.value), Units.VOLT));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.debug("Could not parse batteryVoltage {}", p.value);
|
||||||
|
updateState(CHANNEL_BATTERY_VOLTAGE, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Arrays.stream(apparatusDetail.properties).filter(p -> p.type == 31).findFirst().ifPresent(p -> {
|
||||||
|
try {
|
||||||
|
updateState(CHANNEL_HOURS_OF_PROTECTION, new QuantityType<Time>(Float.parseFloat(p.value), Units.HOUR));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.debug("Could not parse hoursOfProtection {}", p.value);
|
||||||
|
updateState(CHANNEL_HOURS_OF_PROTECTION, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
apparatus.properties.stream().filter(p -> p.type == 3).findFirst().ifPresent(p -> {
|
||||||
|
try {
|
||||||
|
if (p.value.signalStrength != null) {
|
||||||
|
updateState(CHANNEL_SIGNAL_STRENGH, new QuantityType<Dimensionless>(
|
||||||
|
Integer.parseInt(p.value.signalStrength.replaceAll("%", "")), Units.PERCENT));
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.debug("Could not parse signalStrength {}", p.value.signalStrength);
|
||||||
|
updateState(CHANNEL_SIGNAL_STRENGH, UnDefType.UNDEF);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,6 @@
|
|||||||
<type>binding</type>
|
<type>binding</type>
|
||||||
<name>GeneracMobileLink Binding</name>
|
<name>GeneracMobileLink Binding</name>
|
||||||
<description>This binding monitors Generac manufactured generators through the MobileLink cloud service.</description>
|
<description>This binding monitors Generac manufactured generators through the MobileLink cloud service.</description>
|
||||||
|
<connection>cloud</connection>
|
||||||
|
|
||||||
</addon:addon>
|
</addon:addon>
|
||||||
|
@ -23,17 +23,48 @@ thing-type.config.generacmobilelink.generator.generatorId.description = Generato
|
|||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
channel-type.generacmobilelink.batteryVoltage.label = Battery Voltage Status
|
channel-type.generacmobilelink.activationDate.label = Activation Date
|
||||||
channel-type.generacmobilelink.blueLight.label = Blue Light Status
|
channel-type.generacmobilelink.activationDate.description = The activation date of the generator.
|
||||||
channel-type.generacmobilelink.connected.label = Connected
|
channel-type.generacmobilelink.batteryVoltage.label = Battery Voltage
|
||||||
channel-type.generacmobilelink.currentAlarmDescription.label = Current Alarm Description
|
channel-type.generacmobilelink.batteryVoltage.description = The battery voltage.
|
||||||
channel-type.generacmobilelink.exerciseHours.label = Number of Hours Exercised
|
channel-type.generacmobilelink.connectionTime.label = Connection Time
|
||||||
channel-type.generacmobilelink.fuelLevel.label = Fuel Level
|
channel-type.generacmobilelink.connectionTime.description = The date that the unit has been connected from.
|
||||||
channel-type.generacmobilelink.fuelType.label = Fuel Type
|
channel-type.generacmobilelink.deviceSsid.label = Device SSID
|
||||||
channel-type.generacmobilelink.greenLight.label = Green Light Status
|
channel-type.generacmobilelink.deviceSsid.description = The SSID that the generator broadcasts for setup.
|
||||||
channel-type.generacmobilelink.redLight.label = Red Light Status
|
channel-type.generacmobilelink.hasMaintenanceAlert.label = Has Maintenance Alert
|
||||||
channel-type.generacmobilelink.runHours.label = Number of Hours Run
|
channel-type.generacmobilelink.hasMaintenanceAlert.description = Does the generator require maintenance.
|
||||||
channel-type.generacmobilelink.serviceStatus.label = Service Status
|
channel-type.generacmobilelink.heroImageUrl.label = Hero Image URL
|
||||||
|
channel-type.generacmobilelink.heroImageUrl.description = URL to an image of the generator.
|
||||||
|
channel-type.generacmobilelink.hoursOfProtection.label = Hours of Protection
|
||||||
|
channel-type.generacmobilelink.hoursOfProtection.description = Number of hours of protection the generator has provided.
|
||||||
|
channel-type.generacmobilelink.isConnected.label = Is Connected
|
||||||
|
channel-type.generacmobilelink.isConnected.description = Is the unit connected to the cloud service.
|
||||||
|
channel-type.generacmobilelink.isConnecting.label = Is Connecting
|
||||||
|
channel-type.generacmobilelink.isConnecting.description = Is the unit connecting to the cloud service.
|
||||||
|
channel-type.generacmobilelink.lastSeen.label = Last Seen
|
||||||
|
channel-type.generacmobilelink.lastSeen.description = The date that the unit was last connected to the cloud service.
|
||||||
|
channel-type.generacmobilelink.runHours.label = Run Hours
|
||||||
|
channel-type.generacmobilelink.runHours.description = Number of hours run.
|
||||||
|
channel-type.generacmobilelink.showWarning.label = Show Warning
|
||||||
|
channel-type.generacmobilelink.showWarning.description = Should a user interface show a warning symbol due to the current status.
|
||||||
|
channel-type.generacmobilelink.signalStrength.label = Signal Strength
|
||||||
|
channel-type.generacmobilelink.signalStrength.description = The Wi-Fi signal strength of the generator
|
||||||
channel-type.generacmobilelink.status.label = Status
|
channel-type.generacmobilelink.status.label = Status
|
||||||
channel-type.generacmobilelink.statusDate.label = Last Status Date
|
channel-type.generacmobilelink.status.description = The current status of the generator.
|
||||||
channel-type.generacmobilelink.yellowLight.label = Yellow Light Status
|
channel-type.generacmobilelink.status.state.option.1 = Ready
|
||||||
|
channel-type.generacmobilelink.status.state.option.2 = Running
|
||||||
|
channel-type.generacmobilelink.status.state.option.3 = Exercising
|
||||||
|
channel-type.generacmobilelink.status.state.option.4 = Warning
|
||||||
|
channel-type.generacmobilelink.status.state.option.5 = Stopped
|
||||||
|
channel-type.generacmobilelink.status.state.option.6 = Communication Issue
|
||||||
|
channel-type.generacmobilelink.status.state.option.7 = Unknown
|
||||||
|
channel-type.generacmobilelink.statusLabel.label = Status Label
|
||||||
|
channel-type.generacmobilelink.statusLabel.description = The label used to identify the current status.
|
||||||
|
channel-type.generacmobilelink.statusText.label = Status Text
|
||||||
|
channel-type.generacmobilelink.statusText.description = The longer description of the current status.
|
||||||
|
|
||||||
|
# things
|
||||||
|
|
||||||
|
thing.generacmobilelink.account.offline.communication-error.session-expired = Session Expired
|
||||||
|
thing.generacmobilelink.account.offline.configuration-error.invalid-credentials = Invalid Credentials
|
||||||
|
thing.generacmobilelink.account.offline.communication-error.io-exception = Error Communicating with Service
|
||||||
|
@ -17,93 +17,131 @@
|
|||||||
<label>MobileLink Generator</label>
|
<label>MobileLink Generator</label>
|
||||||
<description>MobileLink Generator</description>
|
<description>MobileLink Generator</description>
|
||||||
<channels>
|
<channels>
|
||||||
<channel id="connected" typeId="connected"/>
|
<channel id="heroImageUrl" typeId="heroImageUrl"/>
|
||||||
<channel id="greenLight" typeId="greenLight"/>
|
<channel id="statusLabel" typeId="statusLabel"/>
|
||||||
<channel id="yellowLight" typeId="yellowLight"/>
|
<channel id="statusText" typeId="statusText"/>
|
||||||
<channel id="redLight" typeId="redLight"/>
|
<channel id="activationDate" typeId="activationDate"/>
|
||||||
<channel id="blueLight" typeId="blueLight"/>
|
<channel id="deviceSsid" typeId="deviceSsid"/>
|
||||||
<channel id="statusDate" typeId="statusDate"/>
|
|
||||||
<channel id="status" typeId="status"/>
|
<channel id="status" typeId="status"/>
|
||||||
<channel id="currentAlarmDescription" typeId="currentAlarmDescription"/>
|
<channel id="isConnected" typeId="isConnected"/>
|
||||||
|
<channel id="isConnecting" typeId="isConnecting"/>
|
||||||
|
<channel id="showWarning" typeId="showWarning"/>
|
||||||
|
<channel id="hasMaintenanceAlert" typeId="hasMaintenanceAlert"/>
|
||||||
|
<channel id="lastSeen" typeId="lastSeen"/>
|
||||||
|
<channel id="connectionTime" typeId="connectionTime"/>
|
||||||
<channel id="runHours" typeId="runHours"/>
|
<channel id="runHours" typeId="runHours"/>
|
||||||
<channel id="exerciseHours" typeId="exerciseHours"/>
|
|
||||||
<channel id="fuelType" typeId="fuelType"/>
|
|
||||||
<channel id="fuelLevel" typeId="fuelLevel"/>
|
|
||||||
<channel id="batteryVoltage" typeId="batteryVoltage"/>
|
<channel id="batteryVoltage" typeId="batteryVoltage"/>
|
||||||
<channel id="serviceStatus" typeId="serviceStatus"/>
|
<channel id="hoursOfProtection" typeId="hoursOfProtection"/>
|
||||||
|
<channel id="signalStrength" typeId="signalStrength"/>
|
||||||
</channels>
|
</channels>
|
||||||
<representation-property>generatorId</representation-property>
|
<representation-property>generatorId</representation-property>
|
||||||
<config-description-ref uri="thing-type:generacmobilelink:generator"/>
|
<config-description-ref uri="thing-type:generacmobilelink:generator"/>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
<channel-type id="connected">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Connected</label>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="greenLight">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Green Light Status</label>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="yellowLight">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Yellow Light Status</label>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="redLight">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Red Light Status</label>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="blueLight">
|
|
||||||
<item-type>Switch</item-type>
|
|
||||||
<label>Blue Light Status</label>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="statusDate">
|
|
||||||
<item-type>DateTime</item-type>
|
|
||||||
<label>Last Status Date</label>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="status">
|
<channel-type id="status">
|
||||||
<item-type>String</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Status</label>
|
<label>Status</label>
|
||||||
|
<description>The current status of the generator.</description>
|
||||||
|
<state readOnly="true">
|
||||||
|
<options>
|
||||||
|
<option value="1">Ready</option>
|
||||||
|
<option value="2">Running</option>
|
||||||
|
<option value="3">Exercising</option>
|
||||||
|
<option value="4">Warning</option>
|
||||||
|
<option value="5">Stopped</option>
|
||||||
|
<option value="6">Communication Issue</option>
|
||||||
|
<option value="7">Unknown</option>
|
||||||
|
</options>
|
||||||
|
</state>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="statusLabel">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Status Label</label>
|
||||||
|
<description>The label used to identify the current status.</description>
|
||||||
<state readOnly="true"/>
|
<state readOnly="true"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="currentAlarmDescription">
|
<channel-type id="statusText">
|
||||||
<item-type>String</item-type>
|
<item-type>String</item-type>
|
||||||
<label>Current Alarm Description</label>
|
<label>Status Text</label>
|
||||||
|
<description>The longer description of the current status.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="heroImageUrl">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Hero Image URL</label>
|
||||||
|
<description>URL to an image of the generator.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="activationDate">
|
||||||
|
<item-type>DateTime</item-type>
|
||||||
|
<label>Activation Date</label>
|
||||||
|
<description>The activation date of the generator.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="deviceSsid">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Device SSID</label>
|
||||||
|
<description>The SSID that the generator broadcasts for setup.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="isConnected">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Is Connected</label>
|
||||||
|
<description>Is the unit connected to the cloud service.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="isConnecting">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Is Connecting</label>
|
||||||
|
<description>Is the unit connecting to the cloud service.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="showWarning">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Show Warning</label>
|
||||||
|
<description>Should a user interface show a warning symbol due to the current status.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="hasMaintenanceAlert">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Has Maintenance Alert</label>
|
||||||
|
<description>Does the generator require maintenance.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="lastSeen">
|
||||||
|
<item-type>DateTime</item-type>
|
||||||
|
<label>Last Seen</label>
|
||||||
|
<description>The date that the unit was last connected to the cloud service.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="connectionTime">
|
||||||
|
<item-type>DateTime</item-type>
|
||||||
|
<label>Connection Time</label>
|
||||||
|
<description>The date that the unit has been connected from.</description>
|
||||||
<state readOnly="true"/>
|
<state readOnly="true"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="runHours">
|
<channel-type id="runHours">
|
||||||
<item-type>Number:Time</item-type>
|
<item-type>Number:Time</item-type>
|
||||||
<label>Number of Hours Run</label>
|
<label>Run Hours</label>
|
||||||
|
<description>Number of hours run.</description>
|
||||||
<state readOnly="true" pattern="%d %unit%"/>
|
<state readOnly="true" pattern="%d %unit%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="exerciseHours">
|
|
||||||
<item-type>Number:Time</item-type>
|
|
||||||
<label>Number of Hours Exercised</label>
|
|
||||||
<state readOnly="true" pattern="%d %unit%"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="fuelType">
|
|
||||||
<item-type>Number</item-type>
|
|
||||||
<label>Fuel Type</label>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="fuelLevel">
|
|
||||||
<item-type>Number:Dimensionless</item-type>
|
|
||||||
<label>Fuel Level</label>
|
|
||||||
<state readOnly="true"/>
|
|
||||||
</channel-type>
|
|
||||||
<channel-type id="batteryVoltage">
|
<channel-type id="batteryVoltage">
|
||||||
<item-type>String</item-type>
|
<item-type>Number:ElectricPotential</item-type>
|
||||||
<label>Battery Voltage Status</label>
|
<label>Battery Voltage</label>
|
||||||
<state readOnly="true"/>
|
<description>The battery voltage.</description>
|
||||||
|
<state readOnly="true" pattern="%d %unit%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
<channel-type id="serviceStatus">
|
<channel-type id="hoursOfProtection">
|
||||||
<item-type>Switch</item-type>
|
<item-type>Number:Time</item-type>
|
||||||
<label>Service Status</label>
|
<label>Hours of Protection</label>
|
||||||
<state readOnly="true"/>
|
<description>Number of hours of protection the generator has provided.</description>
|
||||||
|
<state readOnly="true" pattern="%d %unit%"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="signalStrength">
|
||||||
|
<item-type>Number:Dimensionless</item-type>
|
||||||
|
<label>Signal Strength</label>
|
||||||
|
<description>The Wi-Fi signal strength of the generator</description>
|
||||||
|
<state readOnly="true" pattern="%d %unit%"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
Loading…
Reference in New Issue
Block a user