From 6cb9f3a93ee30bece89b0f11cd6314e72e61045d Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Sun, 27 Dec 2020 21:49:30 -0800 Subject: [PATCH] [generacmobilelink| Initial Contribution (#9322) * This is the initial contribution of the Generac MobileLink binding. This allows the Generac line of generators using their MobileLink cloud service to be monitored as things in openHAB. * bump to 3.1 Signed-off-by: Dan Cunningham --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + .../NOTICE | 13 + .../README.md | 95 +++++++ .../pom.xml | 17 ++ .../src/main/feature/feature.xml | 23 ++ .../GeneracMobileLinkBindingConstants.java | 29 ++ ...GeneracMobileLinkAccountConfiguration.java | 27 ++ .../GeneracMobileLinkDiscoveryService.java | 64 +++++ .../internal/dto/ErrorResponseDTO.java | 23 ++ .../internal/dto/GeneratorStatusDTO.java | 54 ++++ .../dto/GeneratorStatusResponseDTO.java | 25 ++ .../internal/dto/LoginRequestDTO.java | 31 +++ .../internal/dto/LoginResponseDTO.java | 23 ++ .../GeneracMobileLinkHandlerFactory.java | 95 +++++++ .../GeneracMobileLinkAccountHandler.java | 251 ++++++++++++++++++ .../GeneracMobileLinkGeneratorHandler.java | 99 +++++++ .../main/resources/OH-INF/binding/binding.xml | 9 + .../main/resources/OH-INF/config/config.xml | 30 +++ .../resources/OH-INF/thing/thing-types.xml | 109 ++++++++ bundles/pom.xml | 1 + 21 files changed, 1024 insertions(+) create mode 100644 bundles/org.openhab.binding.generacmobilelink/NOTICE create mode 100644 bundles/org.openhab.binding.generacmobilelink/README.md create mode 100644 bundles/org.openhab.binding.generacmobilelink/pom.xml create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/GeneracMobileLinkBindingConstants.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/config/GeneracMobileLinkAccountConfiguration.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/discovery/GeneracMobileLinkDiscoveryService.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ErrorResponseDTO.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusDTO.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusResponseDTO.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginRequestDTO.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginResponseDTO.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/factory/GeneracMobileLinkHandlerFactory.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkAccountHandler.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkGeneratorHandler.java create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.generacmobilelink/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index 96f873cd0c5c0..4f4cbc4e7a4a4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -80,6 +80,7 @@ /bundles/org.openhab.binding.ftpupload/ @paulianttila /bundles/org.openhab.binding.gardena/ @gerrieg /bundles/org.openhab.binding.gce/ @clinique +/bundles/org.openhab.binding.generacmobilelink/ @digitaldan /bundles/org.openhab.binding.globalcache/ @mhilbush /bundles/org.openhab.binding.goecharger/ @SamuelBrucksch /bundles/org.openhab.binding.gpstracker/ @gbicskei diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 63d1f4424fe2c..33a19ae51f577 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -386,6 +386,11 @@ org.openhab.binding.gce ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.generacmobilelink + ${project.version} + org.openhab.addons.bundles org.openhab.binding.globalcache diff --git a/bundles/org.openhab.binding.generacmobilelink/NOTICE b/bundles/org.openhab.binding.generacmobilelink/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.generacmobilelink/README.md b/bundles/org.openhab.binding.generacmobilelink/README.md new file mode 100644 index 0000000000000..adb7ccd1e4080 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/README.md @@ -0,0 +1,95 @@ +# Generac MobileLink Binding + +This binding communicates with the Generac MobileLink API and reports on the status of Generac manufactured generators, including versions resold under the brands Eaton, Honeywell and Siemens. + +## Supported Things + + +### MobileLink Account + +A MobileLink account bridge thing represents a user's MobileLink account and is responsible for authentication and polling for updates. + +ThingTypeUID: `account` + +### Generator + +A Generator thing represents a individual generator linked to an account bridge. Multiple generators are supported. + +ThingTypeUID: `generator` + +## Discovery + +The MobileLink account bridge must be added manually. Once added, generator things will automatically be added to the inbox. + +## Thing Configuration + +### MobileLink Account + +| Parameter | Description | +|-----------------|------------------------------------------------------------------------------------| +| username | The user name, typically an email address, used to login to the MobileLink service | +| password | The password used to login to the MobileLink service | +| refreshInterval | The frequency to poll for generator updates, minimum duration is 30 seconds | + + +## Channels + +### Generator Channels + +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 | + + +## Full Example + +### Things + +```xtend +Bridge generacmobilelink:account:main "MobileLink Account" [ userName="foo@bar.com", password="secret",refreshInterval=60 ] { + Thing generator 123456 "MobileLink Generator" [ generatorId="123456" ] +} +``` + +### Items + +```xtend +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"} +``` + +### Sitemap + +```xtend +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 + } +} +``` diff --git a/bundles/org.openhab.binding.generacmobilelink/pom.xml b/bundles/org.openhab.binding.generacmobilelink/pom.xml new file mode 100644 index 0000000000000..ca299fd519781 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.1.0-SNAPSHOT + + + org.openhab.binding.generacmobilelink + + openHAB Add-ons :: Bundles :: GeneracMobileLink Binding + + diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/feature/feature.xml b/bundles/org.openhab.binding.generacmobilelink/src/main/feature/feature.xml new file mode 100644 index 0000000000000..71ed25e54b3f3 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/feature/feature.xml @@ -0,0 +1,23 @@ + + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.generacmobilelink/${project.version} + + diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/GeneracMobileLinkBindingConstants.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/GeneracMobileLinkBindingConstants.java new file mode 100644 index 0000000000000..e27e02bf3dba0 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/GeneracMobileLinkBindingConstants.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2010-2020 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; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link GeneracMobileLinkBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Dan Cunningham - Initial contribution + */ +@NonNullByDefault +public class GeneracMobileLinkBindingConstants { + private 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"); +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/config/GeneracMobileLinkAccountConfiguration.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/config/GeneracMobileLinkAccountConfiguration.java new file mode 100644 index 0000000000000..def1179060b58 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/config/GeneracMobileLinkAccountConfiguration.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2020 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.config; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link GeneracMobileLinkAccountConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Dan Cunningham - Initial contribution + */ +@NonNullByDefault +public class GeneracMobileLinkAccountConfiguration { + public String username = ""; + public String password = ""; + public Integer refreshInterval = 60; +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/discovery/GeneracMobileLinkDiscoveryService.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/discovery/GeneracMobileLinkDiscoveryService.java new file mode 100644 index 0000000000000..c0de87f4437cd --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/discovery/GeneracMobileLinkDiscoveryService.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2020 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.discovery; + +import static org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants.THING_TYPE_GENERATOR; + +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.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; + +/** + * The {@link GeneracMobileLinkDiscoveryService} is responsible for discovering generator things + * + * @author Dan Cunningham - Initial contribution + */ +@NonNullByDefault +public class GeneracMobileLinkDiscoveryService extends AbstractDiscoveryService { + private static final Set SUPPORTED_DISCOVERY_THING_TYPES_UIDS = Set.of(THING_TYPE_GENERATOR); + + public GeneracMobileLinkDiscoveryService() { + super(SUPPORTED_DISCOVERY_THING_TYPES_UIDS, 0); + } + + @Override + public Set getSupportedThingTypes() { + return SUPPORTED_DISCOVERY_THING_TYPES_UIDS; + } + + @Override + public void startScan() { + } + + @Override + public boolean isBackgroundDiscoveryEnabled() { + return false; + } + + public void generatorDiscovered(GeneratorStatusDTO generator, 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(); + thingDiscovered(result); + } +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ErrorResponseDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ErrorResponseDTO.java new file mode 100644 index 0000000000000..ee8abfe1c5783 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/ErrorResponseDTO.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2010-2020 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 ErrorResponseDTO} object from the MobileLink API + * + * @author Dan Cunningham - Initial contribution + */ +public class ErrorResponseDTO { + public Integer errorCode; + public String errorMessage; +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusDTO.java new file mode 100644 index 0000000000000..299954dda683f --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusDTO.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.generacmobilelink.internal.dto; + +/** + * {@link GeneratorStatusDTO} object from the MobileLink API + * + * @author Dan Cunningham - Initial contribution + */ +public class GeneratorStatusDTO { + public Integer gensetID; + public String generatorDate; + public String generatorName; + public String generatorSerialNumber; + public String generatorModel; + public String generatorDescription; + public String generatorMDN; + public String generatorImei; + public String generatorIccid; + public String generatorTetherSerial; + public Boolean connected; + public Boolean greenLightLit; + public Boolean yellowLightLit; + public Boolean redLightLit; + public Boolean blueLightLit; + public String generatorStatus; + public String generatorStatusDate; + public String currentAlarmDescription; + public Integer runHours; + public Integer exerciseHours; + public String batteryVoltage; + public Integer fuelType; + public Integer fuelLevel; + public String generatorBrandImageURL; + public Boolean generatorServiceStatus; + public String signalStrength; + public String deviceId; + public Integer deviceTypeId; + public String firmwareVersion; + public String timezone; + public String mACAddress; + public String iPAddress; + public String sSID; +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusResponseDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusResponseDTO.java new file mode 100644 index 0000000000000..645517d0611a6 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/GeneratorStatusResponseDTO.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2020 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.ArrayList; + +/** + * {@link GeneratorStatusResponseDTO} response from the MobileLink API + * + * @author Dan Cunningham - Initial contribution + */ +@SuppressWarnings("serial") +public class GeneratorStatusResponseDTO extends ArrayList { + +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginRequestDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginRequestDTO.java new file mode 100644 index 0000000000000..a47deccebbb4a --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginRequestDTO.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.generacmobilelink.internal.dto; + +/** + * {@link LoginRequestDTO} request for the MobileLink API + * + * @author Dan Cunningham - Initial contribution + */ +public class LoginRequestDTO { + public LoginRequestDTO(String sharedKey, String userLogin, String userPassword) { + super(); + this.sharedKey = sharedKey; + this.userLogin = userLogin; + this.userPassword = userPassword; + } + + public String sharedKey; + public String userLogin; + public String userPassword; +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginResponseDTO.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginResponseDTO.java new file mode 100644 index 0000000000000..b8382c8e336a4 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/dto/LoginResponseDTO.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.generacmobilelink.internal.dto; + +/** + * {@link LoginResponseDTO} response from the MobileLink API + * + * @author Dan Cunningham - Initial contribution + */ +public class LoginResponseDTO { + public String authToken; + public String pushChannelName; +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/factory/GeneracMobileLinkHandlerFactory.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/factory/GeneracMobileLinkHandlerFactory.java new file mode 100644 index 0000000000000..292e1929809f9 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/factory/GeneracMobileLinkHandlerFactory.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2020 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.factory; + +import static org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants.*; + +import java.util.Hashtable; +import java.util.Map; +import java.util.Set; +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; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link GeneracMobileLinkHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Dan Cunningham - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.generacmobilelink", service = ThingHandlerFactory.class) +public class GeneracMobileLinkHandlerFactory extends BaseThingHandlerFactory { + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, + THING_TYPE_GENERATOR); + private final Map> discoveryServiceRegs = new ConcurrentHashMap<>(); + private final HttpClient httpClient; + + @Activate + public GeneracMobileLinkHandlerFactory(final @Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_GENERATOR.equals(thingTypeUID)) { + return new GeneracMobileLinkGeneratorHandler(thing); + } + + if (THING_TYPE_ACCOUNT.equals(thingTypeUID)) { + GeneracMobileLinkDiscoveryService discoveryService = new GeneracMobileLinkDiscoveryService(); + GeneracMobileLinkAccountHandler accountHandler = new GeneracMobileLinkAccountHandler((Bridge) thing, + httpClient, discoveryService); + discoveryServiceRegs.put(accountHandler.getThing().getUID(), bundleContext + .registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); + return accountHandler; + } + + return null; + } + + @Override + protected synchronized void removeHandler(ThingHandler thingHandler) { + if (thingHandler instanceof GeneracMobileLinkAccountHandler) { + ServiceRegistration serviceReg = discoveryServiceRegs.remove(thingHandler.getThing().getUID()); + if (serviceReg != null) { + serviceReg.unregister(); + } + } + } +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkAccountHandler.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkAccountHandler.java new file mode 100644 index 0000000000000..c19b5a2520665 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkAccountHandler.java @@ -0,0 +1,251 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +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 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.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.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants; +import org.openhab.binding.generacmobilelink.internal.config.GeneracMobileLinkAccountConfiguration; +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.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; +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; + +/** + * The {@link GeneracMobileLinkAccountHandler} is responsible for connecting to the MobileLink cloud service and + * discovering generator things + * + * @author Dan Cunningham - Initial contribution + */ +@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, + GeneracMobileLinkDiscoveryService discoveryService) { + super(bridge); + this.httpClient = httpClient; + this.discoveryService = discoveryService; + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + authToken = null; + restartPoll(); + } + + @Override + public void dispose() { + stopPoll(); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + updateGeneratorThings(); + } + } + + @Override + public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) { + GeneratorStatusResponseDTO generatorsLocal = generators; + if (generatorsLocal != null) { + Optional generatorOpt = generatorsLocal.stream() + .filter(g -> String.valueOf(g.gensetID).equals(childThing.getUID().getId())).findFirst(); + if (generatorOpt.isPresent()) { + ((GeneracMobileLinkGeneratorHandler) childHandler).updateGeneratorStatus(generatorOpt.get()); + } + } + } + + private void stopPoll() { + Future localPollFuture = pollFuture; + if (localPollFuture != null) { + localPollFuture.cancel(true); + } + } + + private void restartPoll() { + stopPoll(); + pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshIntervalSeconds, TimeUnit.SECONDS); + } + + private void poll() { + try { + if (authToken == null) { + logger.debug("Attempting Login"); + login(); + } + getStatuses(true); + } catch (InterruptedException e) { + } + } + + 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) { + 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); + } + } else { + if (retry) { + logger.debug("Retrying status request"); + getStatuses(false); + } else { + handleErrorResponse(result); + } + } + } + + private HTTPResult sendRequest(String url, HttpMethod method, @Nullable String token, + @Nullable ContentProvider content, @Nullable String contentType) throws InterruptedException { + try { + Request request = httpClient.newRequest(url).method(method).timeout(10, TimeUnit.SECONDS); + if (token != null) { + request = request.header("AuthToken", token); + } + if (content != null & contentType != null) { + request = request.content(content, contentType); + } + logger.trace("Sending {} to {}", request.getMethod(), request.getURI()); + final CompletableFuture futureResult = new CompletableFuture<>(); + request.send(new BufferingResponseListener() { + @NonNullByDefault({}) + @Override + public void onComplete(Result result) { + futureResult.complete(new HTTPResult(result.getResponse().getStatus(), getContentAsString())); + } + }); + HTTPResult result = futureResult.get(); + logger.trace("Response - status: {} content: {}", result.responseCode, result.content); + return result; + } catch (ExecutionException e) { + return new HTTPResult(0, e.getMessage()); + } + } + + 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); + } + } + + 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); + } + } + }); + } + } + + public static class HTTPResult { + public @Nullable String content; + public final int responseCode; + + public HTTPResult(int responseCode, @Nullable String content) { + this.responseCode = responseCode; + this.content = content; + } + } +} diff --git a/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkGeneratorHandler.java b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkGeneratorHandler.java new file mode 100644 index 0000000000000..b12d1684a62a7 --- /dev/null +++ b/bundles/org.openhab.binding.generacmobilelink/src/main/java/org/openhab/binding/generacmobilelink/internal/handler/GeneracMobileLinkGeneratorHandler.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2020 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.handler; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +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.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link GeneracMobileLinkGeneratorHandler} is responsible for updating a generator things's channels + * + * @author Dan Cunningham - Initial contribution + */ +@NonNullByDefault +public class GeneracMobileLinkGeneratorHandler extends BaseThingHandler { + private final Logger logger = LoggerFactory.getLogger(GeneracMobileLinkGeneratorHandler.class); + private @Nullable GeneratorStatusDTO status; + + public GeneracMobileLinkGeneratorHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + updateState(); + } + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + } + + protected void updateGeneratorStatus(GeneratorStatusDTO status) { + this.status = status; + 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