mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +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.
|
||||
|
||||
| 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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -14,4 +14,12 @@
|
||||
|
||||
<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>
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
<feature name="openhab-binding-generacmobilelink" description="Generac MobileLink Binding" version="${project.version}">
|
||||
<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>
|
||||
</feature>
|
||||
</features>
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -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<GeneratorStatusDTO> {
|
||||
@NonNullByDefault
|
||||
public class GeneracMobileLinkGeneratorConfiguration {
|
||||
|
||||
public String generatorId = "";
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
}
|
@ -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.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<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT,
|
||||
THING_TYPE_GENERATOR);
|
||||
private final Map<ThingUID, ServiceRegistration<?>> 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;
|
||||
|
@ -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<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) {
|
||||
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<GeneratorStatusDTO> 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> 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> T getEndpoint(Class<T> 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<HTTPResult> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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));
|
||||
private void updateState() {
|
||||
Apparatus apparatus = this.apparatus;
|
||||
ApparatusDetail apparatusDetail = this.apparatusDetail;
|
||||
if (apparatus == null || apparatusDetail == null) {
|
||||
return;
|
||||
}
|
||||
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>
|
||||
<name>GeneracMobileLink Binding</name>
|
||||
<description>This binding monitors Generac manufactured generators through the MobileLink cloud service.</description>
|
||||
<connection>cloud</connection>
|
||||
|
||||
</addon:addon>
|
||||
|
@ -23,17 +23,48 @@ thing-type.config.generacmobilelink.generator.generatorId.description = Generato
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.generacmobilelink.batteryVoltage.label = Battery Voltage Status
|
||||
channel-type.generacmobilelink.blueLight.label = Blue Light Status
|
||||
channel-type.generacmobilelink.connected.label = Connected
|
||||
channel-type.generacmobilelink.currentAlarmDescription.label = Current Alarm Description
|
||||
channel-type.generacmobilelink.exerciseHours.label = Number of Hours Exercised
|
||||
channel-type.generacmobilelink.fuelLevel.label = Fuel Level
|
||||
channel-type.generacmobilelink.fuelType.label = Fuel Type
|
||||
channel-type.generacmobilelink.greenLight.label = Green Light Status
|
||||
channel-type.generacmobilelink.redLight.label = Red Light Status
|
||||
channel-type.generacmobilelink.runHours.label = Number of Hours Run
|
||||
channel-type.generacmobilelink.serviceStatus.label = Service Status
|
||||
channel-type.generacmobilelink.activationDate.label = Activation Date
|
||||
channel-type.generacmobilelink.activationDate.description = The activation date of the generator.
|
||||
channel-type.generacmobilelink.batteryVoltage.label = Battery Voltage
|
||||
channel-type.generacmobilelink.batteryVoltage.description = The battery voltage.
|
||||
channel-type.generacmobilelink.connectionTime.label = Connection Time
|
||||
channel-type.generacmobilelink.connectionTime.description = The date that the unit has been connected from.
|
||||
channel-type.generacmobilelink.deviceSsid.label = Device SSID
|
||||
channel-type.generacmobilelink.deviceSsid.description = The SSID that the generator broadcasts for setup.
|
||||
channel-type.generacmobilelink.hasMaintenanceAlert.label = Has Maintenance Alert
|
||||
channel-type.generacmobilelink.hasMaintenanceAlert.description = Does the generator require maintenance.
|
||||
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.statusDate.label = Last Status Date
|
||||
channel-type.generacmobilelink.yellowLight.label = Yellow Light Status
|
||||
channel-type.generacmobilelink.status.description = The current status of the generator.
|
||||
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>
|
||||
<description>MobileLink Generator</description>
|
||||
<channels>
|
||||
<channel id="connected" typeId="connected"/>
|
||||
<channel id="greenLight" typeId="greenLight"/>
|
||||
<channel id="yellowLight" typeId="yellowLight"/>
|
||||
<channel id="redLight" typeId="redLight"/>
|
||||
<channel id="blueLight" typeId="blueLight"/>
|
||||
<channel id="statusDate" typeId="statusDate"/>
|
||||
<channel id="heroImageUrl" typeId="heroImageUrl"/>
|
||||
<channel id="statusLabel" typeId="statusLabel"/>
|
||||
<channel id="statusText" typeId="statusText"/>
|
||||
<channel id="activationDate" typeId="activationDate"/>
|
||||
<channel id="deviceSsid" typeId="deviceSsid"/>
|
||||
<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="exerciseHours" typeId="exerciseHours"/>
|
||||
<channel id="fuelType" typeId="fuelType"/>
|
||||
<channel id="fuelLevel" typeId="fuelLevel"/>
|
||||
<channel id="batteryVoltage" typeId="batteryVoltage"/>
|
||||
<channel id="serviceStatus" typeId="serviceStatus"/>
|
||||
<channel id="hoursOfProtection" typeId="hoursOfProtection"/>
|
||||
<channel id="signalStrength" typeId="signalStrength"/>
|
||||
</channels>
|
||||
<representation-property>generatorId</representation-property>
|
||||
<config-description-ref uri="thing-type:generacmobilelink:generator"/>
|
||||
</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">
|
||||
<item-type>String</item-type>
|
||||
<item-type>Number</item-type>
|
||||
<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"/>
|
||||
</channel-type>
|
||||
<channel-type id="currentAlarmDescription">
|
||||
<channel-type id="statusText">
|
||||
<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"/>
|
||||
</channel-type>
|
||||
<channel-type id="runHours">
|
||||
<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%"/>
|
||||
</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">
|
||||
<item-type>String</item-type>
|
||||
<label>Battery Voltage Status</label>
|
||||
<state readOnly="true"/>
|
||||
<item-type>Number:ElectricPotential</item-type>
|
||||
<label>Battery Voltage</label>
|
||||
<description>The battery voltage.</description>
|
||||
<state readOnly="true" pattern="%d %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="serviceStatus">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Service Status</label>
|
||||
<state readOnly="true"/>
|
||||
<channel-type id="hoursOfProtection">
|
||||
<item-type>Number:Time</item-type>
|
||||
<label>Hours of Protection</label>
|
||||
<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>
|
||||
</thing:thing-descriptions>
|
||||
|
Loading…
Reference in New Issue
Block a user