[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 <dan@digitaldan.com>
This commit is contained in:
Dan Cunningham 2020-12-27 21:49:30 -08:00 committed by GitHub
parent 03dd4d491f
commit 6cb9f3a93e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1024 additions and 0 deletions

View File

@ -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

View File

@ -386,6 +386,11 @@
<artifactId>org.openhab.binding.gce</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.generacmobilelink</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.globalcache</artifactId>

View File

@ -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

View File

@ -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
}
}
```

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.1.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.generacmobilelink</artifactId>
<name>openHAB Add-ons :: Bundles :: GeneracMobileLink Binding</name>
</project>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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
-->
<features name="org.openhab.binding.generacmobilelink-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-generacmobilelink" description="Generac MobileLink Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.generacmobilelink/${project.version}</bundle>
</feature>
</features>

View File

@ -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");
}

View File

@ -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;
}

View File

@ -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<ThingTypeUID> SUPPORTED_DISCOVERY_THING_TYPES_UIDS = Set.of(THING_TYPE_GENERATOR);
public GeneracMobileLinkDiscoveryService() {
super(SUPPORTED_DISCOVERY_THING_TYPES_UIDS, 0);
}
@Override
public Set<ThingTypeUID> 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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<GeneratorStatusDTO> {
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<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;
@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();
}
}
}
}

View File

@ -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<GeneratorStatusDTO> 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<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());
}
}
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;
}
}
}

View File

@ -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<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));
}
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="generacmobilelink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>GeneracMobileLink Binding</name>
<description>This binding monitors Generac manufactured generators through the MobileLink cloud service.</description>
</binding:binding>

View File

@ -0,0 +1,30 @@
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:generacmobilelink:account">
<parameter name="username" type="text" required="true">
<label>Username</label>
<description>Account username</description>
</parameter>
<parameter name="password" type="text" required="true">
<label>Password</label>
<description>Account password</description>
<context>password</context>
</parameter>
<parameter name="refreshInterval" type="integer" min="30" required="true" unit="s">
<label>Refresh Interval</label>
<description>Specifies the refresh interval in seconds</description>
<default>60</default>
</parameter>
</config-description>
<config-description uri="thing-type:generacmobilelink:generator">
<parameter name="generatorId" type="text" required="true">
<label>Generator ID</label>
<description>Generator ID</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="generacmobilelink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="account">
<label>MobileLink Account</label>
<description>MobileLink Cloud Account</description>
<config-description-ref uri="thing-type:generacmobilelink:account"/>
</bridge-type>
<thing-type id="generator">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<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="status" typeId="status"/>
<channel id="currentAlarmDescription" typeId="currentAlarmDescription"/>
<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"/>
</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>
<label>Status</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="currentAlarmDescription">
<item-type>String</item-type>
<label>Current Alarm Description</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="runHours">
<item-type>Number:Time</item-type>
<label>Number of Hours Run</label>
<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"/>
</channel-type>
<channel-type id="serviceStatus">
<item-type>Switch</item-type>
<label>Service Status</label>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>

View File

@ -111,6 +111,7 @@
<module>org.openhab.binding.ftpupload</module>
<module>org.openhab.binding.gardena</module>
<module>org.openhab.binding.gce</module>
<module>org.openhab.binding.generacmobilelink</module>
<module>org.openhab.binding.goecharger</module>
<module>org.openhab.binding.globalcache</module>
<module>org.openhab.binding.gpstracker</module>