added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.solaredge-${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-solaredge" description="SolarEdge Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.solaredge/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.solaredge.internal;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* trait class which contains useful helper methods. Thus, the interface can be implemented and methods are available
|
||||
* within the class.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface AtomicReferenceTrait {
|
||||
|
||||
/**
|
||||
* this should usually not called directly. use updateJobReference or cancelJobReference instead
|
||||
*
|
||||
* @param job job to cancel.
|
||||
*/
|
||||
default void cancelJob(@Nullable Future<?> job) {
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* updates a job reference with a new job. the old job will be cancelled if there is one.
|
||||
*
|
||||
* @param jobReference reference to be updated
|
||||
* @param newJob job to be assigned
|
||||
*/
|
||||
default void updateJobReference(AtomicReference<@Nullable Future<?>> jobReference, Future<?> newJob) {
|
||||
cancelJob(jobReference.getAndSet(newJob));
|
||||
}
|
||||
|
||||
/**
|
||||
* updates a job reference to null and cancels any existing job which might be assigned to the reference.
|
||||
*
|
||||
* @param jobReference to be updated to null.
|
||||
*/
|
||||
default void cancelJobReference(AtomicReference<@Nullable Future<?>> jobReference) {
|
||||
cancelJob(jobReference.getAndSet(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.solaredge.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link SolarEdgeBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Alexander Friese - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SolarEdgeBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "solaredge";
|
||||
|
||||
// List of main device types
|
||||
public static final String DEVICE_GENERIC = "generic";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_GENERIC = new ThingTypeUID(BINDING_ID, DEVICE_GENERIC);
|
||||
|
||||
// List of all channel groups
|
||||
public static final String CHANNEL_GROUP_LIVE = "live";
|
||||
public static final String CHANNEL_GROUP_AGGREGATE_DAY = "aggregate_day";
|
||||
public static final String CHANNEL_GROUP_AGGREGATE_WEEK = "aggregate_week";
|
||||
public static final String CHANNEL_GROUP_AGGREGATE_MONTH = "aggregate_month";
|
||||
public static final String CHANNEL_GROUP_AGGREGATE_YEAR = "aggregate_year";
|
||||
|
||||
// List of all channel ids
|
||||
public static final String CHANNEL_ID_PRODUCTION = "production";
|
||||
public static final String CHANNEL_ID_PV_STATUS = "pv_status";
|
||||
public static final String CHANNEL_ID_CONSUMPTION = "consumption";
|
||||
public static final String CHANNEL_ID_LOAD_STATUS = "load_status";
|
||||
public static final String CHANNEL_ID_BATTERY_STATUS = "battery_status";
|
||||
public static final String CHANNEL_ID_BATTERY_CRITICAL = "battery_critical";
|
||||
public static final String CHANNEL_ID_BATTERY_LEVEL = "battery_level";
|
||||
public static final String CHANNEL_ID_GRID_STATUS = "grid_status";
|
||||
public static final String CHANNEL_ID_IMPORT = "import";
|
||||
public static final String CHANNEL_ID_EXPORT = "export";
|
||||
public static final String CHANNEL_ID_BATTERY_CHARGE = "battery_charge";
|
||||
public static final String CHANNEL_ID_BATTERY_DISCHARGE = "battery_discharge";
|
||||
public static final String CHANNEL_ID_BATTERY_CHARGE_DISCHARGE = "battery_charge_discharge";
|
||||
public static final String CHANNEL_ID_SELF_CONSUMPTION_FOR_CONSUMPTION = "selfConsumptionForConsumption";
|
||||
public static final String CHANNEL_ID_SELF_CONSUMPTION_COVERAGE = "selfConsumptionCoverage";
|
||||
public static final String CHANNEL_ID_BATTERY_SELF_CONSUMPTION = "batterySelfConsumption";
|
||||
|
||||
// PRIVATE API CONSTANTS
|
||||
// URLs
|
||||
public static final String PRIVATE_DATA_API_URL = "https://monitoring.solaredge.com/solaredge-apigw/api/site/";
|
||||
public static final String PRIVATE_DATA_API_URL_AGGREGATE_DATA_DAY_WEEK_SUFFIX = "/powerDashboardChart";
|
||||
public static final String PRIVATE_DATA_API_URL_AGGREGATE_DATA_MONTH_YEAR_SUFFIX = "/energyDashboardChart";
|
||||
public static final String PRIVATE_DATA_API_URL_LIVE_DATA_SUFFIX = "/currentPowerFlow.json";
|
||||
|
||||
// field names
|
||||
public static final String PRIVATE_API_TOKEN_COOKIE_NAME = "SPRING_SECURITY_REMEMBER_ME_COOKIE";
|
||||
public static final String PRIVATE_API_TOKEN_COOKIE_DOMAIN = "monitoring.solaredge.com";
|
||||
public static final String PRIVATE_API_TOKEN_COOKIE_PATH = "/";
|
||||
public static final String PRIVATE_DATA_API_AGGREGATE_DATA_CHARTFIELD_FIELD = "chartField";
|
||||
|
||||
//
|
||||
//
|
||||
// PRIVATE API CONSTANTS
|
||||
// URLs
|
||||
public static final String PUBLIC_DATA_API_URL = "https://monitoringapi.solaredge.com/site/";
|
||||
public static final String PUBLIC_DATA_API_URL_AGGREGATE_DATA_SUFFIX = "/energyDetails";
|
||||
public static final String PUBLIC_DATA_API_URL_LIVE_DATA_SUFFIX = "/currentPowerFlow";
|
||||
public static final String PUBLIC_DATA_API_URL_LIVE_DATA_METERLESS_SUFFIX = "/overview";
|
||||
|
||||
// field names
|
||||
public static final String PUBLIC_DATA_API_KEY_FIELD = "api_key";
|
||||
public static final String PUBLIC_DATA_API_START_TIME_FIELD = "startTime";
|
||||
public static final String PUBLIC_DATA_API_END_TIME_FIELD = "endTime";
|
||||
public static final String PUBLIC_DATA_API_TIME_UNIT_FIELD = "timeUnit";
|
||||
|
||||
// constants
|
||||
public static final String BEGIN_OF_DAY_TIME = "00:00:00";
|
||||
public static final String END_OF_DAY_TIME = "23:59:59";
|
||||
public static final long MINUTES_PER_DAY = 1440;
|
||||
|
||||
// web request constants
|
||||
public static final long WEB_REQUEST_PUBLIC_API_DAY_LIMIT = 300;
|
||||
public static final long WEB_REQUEST_INITIAL_DELAY = TimeUnit.SECONDS.toMillis(30);
|
||||
public static final long WEB_REQUEST_INTERVAL = TimeUnit.SECONDS.toMillis(5);
|
||||
public static final int WEB_REQUEST_QUEUE_MAX_SIZE = 20;
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_GENERIC);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.solaredge.internal;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.solaredge.internal.handler.GenericSolarEdgeHandler;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SolarEdgeHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Alexander Friese - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.solaredge")
|
||||
public class SolarEdgeHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SolarEdgeHandlerFactory.class);
|
||||
|
||||
/**
|
||||
* the shared http client
|
||||
*/
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public SolarEdgeHandlerFactory(@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 (thingTypeUID.equals(THING_TYPE_GENERIC)) {
|
||||
return new GenericSolarEdgeHandler(thing, httpClient);
|
||||
} else {
|
||||
logger.warn("Unsupported Thing-Type: {}", thingTypeUID.getAsString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 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.solaredge.internal.callback;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.net.CookieStore;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpStatus.Code;
|
||||
import org.openhab.binding.solaredge.internal.command.SolarEdgeCommand;
|
||||
import org.openhab.binding.solaredge.internal.config.SolarEdgeConfiguration;
|
||||
import org.openhab.binding.solaredge.internal.connector.CommunicationStatus;
|
||||
import org.openhab.binding.solaredge.internal.connector.StatusUpdateListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* base class for all commands. common logic should be implemented here
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractCommandCallback extends BufferingResponseListener implements SolarEdgeCommand {
|
||||
|
||||
/**
|
||||
* logger
|
||||
*/
|
||||
protected final Logger logger = LoggerFactory.getLogger(AbstractCommandCallback.class);
|
||||
|
||||
/**
|
||||
* the configuration
|
||||
*/
|
||||
protected final SolarEdgeConfiguration config;
|
||||
|
||||
/**
|
||||
* JSON deserializer
|
||||
*/
|
||||
private final Gson gson;
|
||||
|
||||
/**
|
||||
* status code of fulfilled request
|
||||
*/
|
||||
private final CommunicationStatus communicationStatus;
|
||||
|
||||
/**
|
||||
* listener to provide updates to the WebInterface class
|
||||
*/
|
||||
private @Nullable StatusUpdateListener listener;
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
public AbstractCommandCallback(SolarEdgeConfiguration config) {
|
||||
this.communicationStatus = new CommunicationStatus();
|
||||
this.config = config;
|
||||
this.gson = new Gson();
|
||||
}
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
public AbstractCommandCallback(SolarEdgeConfiguration config, StatusUpdateListener listener) {
|
||||
this(config);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log request success
|
||||
*/
|
||||
@Override
|
||||
public final void onSuccess(@Nullable Response response) {
|
||||
super.onSuccess(response);
|
||||
if (response != null) {
|
||||
communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
|
||||
logger.debug("HTTP response {}", response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log request failure
|
||||
*/
|
||||
@Override
|
||||
public final void onFailure(@Nullable Response response, @Nullable Throwable failure) {
|
||||
super.onFailure(response, failure);
|
||||
if (failure != null) {
|
||||
logger.debug("Request failed: {}", failure.toString());
|
||||
communicationStatus.setError((Exception) failure);
|
||||
|
||||
if (failure instanceof SocketTimeoutException || failure instanceof TimeoutException) {
|
||||
communicationStatus.setHttpCode(Code.REQUEST_TIMEOUT);
|
||||
} else if (failure instanceof UnknownHostException) {
|
||||
communicationStatus.setHttpCode(Code.BAD_GATEWAY);
|
||||
} else {
|
||||
communicationStatus.setHttpCode(Code.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Request failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContent(@Nullable Response response, @Nullable ByteBuffer content) {
|
||||
super.onContent(response, content);
|
||||
logger.debug("received content, length: {}", getContentAsString().length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performAction(HttpClient asyncclient) {
|
||||
Request request = asyncclient.newRequest(getURL()).timeout(config.getAsyncTimeout(), TimeUnit.SECONDS);
|
||||
|
||||
// add authentication data for every request. Handling this here makes it obsolete to implement for each and
|
||||
// every command
|
||||
if (config.isUsePrivateApi()) {
|
||||
// token cookie is only used by private API therefore this can be skipped when using public API
|
||||
CookieStore cookieStore = asyncclient.getCookieStore();
|
||||
HttpCookie c = new HttpCookie(PRIVATE_API_TOKEN_COOKIE_NAME, config.getTokenOrApiKey());
|
||||
c.setDomain(PRIVATE_API_TOKEN_COOKIE_DOMAIN);
|
||||
c.setPath(PRIVATE_API_TOKEN_COOKIE_PATH);
|
||||
cookieStore.add(URI.create(getURL()), c);
|
||||
} else {
|
||||
// this is only relevant when using public API
|
||||
request.param(PUBLIC_DATA_API_KEY_FIELD, config.getTokenOrApiKey());
|
||||
|
||||
}
|
||||
|
||||
prepareRequest(request).send(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return returns Http Status Code
|
||||
*/
|
||||
public CommunicationStatus getCommunicationStatus() {
|
||||
return communicationStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListenerStatus() {
|
||||
if (listener != null) {
|
||||
listener.update(communicationStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* concrete implementation has to prepare the requests with additional parameters, etc
|
||||
*
|
||||
* @param requestToPrepare the request to prepare
|
||||
* @return prepared Request object
|
||||
*/
|
||||
protected abstract Request prepareRequest(Request requestToPrepare);
|
||||
|
||||
/**
|
||||
* concrete implementation has to provide the URL
|
||||
*
|
||||
* @return Url
|
||||
*/
|
||||
protected abstract String getURL();
|
||||
|
||||
@Override
|
||||
public final void setListener(StatusUpdateListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* just a wrapper as fromJson could return null. This will avoid warnings as eclipse otherwise assumes unnecessary
|
||||
* null checks which are not unnecessary
|
||||
*
|
||||
* @param <T>
|
||||
* @param json
|
||||
* @param classOfT
|
||||
* @return
|
||||
* @throws JsonSyntaxException
|
||||
*/
|
||||
protected <T> @Nullable T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
|
||||
return gson.fromJson(json, classOfT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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.solaredge.internal.command;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.solaredge.internal.callback.AbstractCommandCallback;
|
||||
import org.openhab.binding.solaredge.internal.handler.SolarEdgeHandler;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePrivateApi;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponseTransformerPrivateApi;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregatePeriod;
|
||||
|
||||
/**
|
||||
* command that retrieves status values for aggregate data channels via private API
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AggregateDataUpdatePrivateApi extends AbstractCommandCallback implements SolarEdgeCommand {
|
||||
|
||||
/**
|
||||
* the solaredge handler
|
||||
*/
|
||||
private final SolarEdgeHandler handler;
|
||||
private final AggregateDataResponseTransformerPrivateApi transformer;
|
||||
|
||||
/**
|
||||
* data aggregation level
|
||||
*/
|
||||
private final AggregatePeriod period;
|
||||
|
||||
/**
|
||||
* url suffix depending on aggregation level
|
||||
*/
|
||||
private final String urlSuffix;
|
||||
private int retries = 0;
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*
|
||||
* @param handler
|
||||
* @param period
|
||||
*/
|
||||
public AggregateDataUpdatePrivateApi(SolarEdgeHandler handler, AggregatePeriod period) {
|
||||
super(handler.getConfiguration());
|
||||
this.handler = handler;
|
||||
this.transformer = new AggregateDataResponseTransformerPrivateApi(handler);
|
||||
this.period = period;
|
||||
switch (period) {
|
||||
case DAY:
|
||||
this.urlSuffix = PRIVATE_DATA_API_URL_AGGREGATE_DATA_DAY_WEEK_SUFFIX;
|
||||
break;
|
||||
case WEEK:
|
||||
this.urlSuffix = PRIVATE_DATA_API_URL_AGGREGATE_DATA_DAY_WEEK_SUFFIX;
|
||||
break;
|
||||
case MONTH:
|
||||
this.urlSuffix = PRIVATE_DATA_API_URL_AGGREGATE_DATA_MONTH_YEAR_SUFFIX;
|
||||
break;
|
||||
case YEAR:
|
||||
this.urlSuffix = PRIVATE_DATA_API_URL_AGGREGATE_DATA_MONTH_YEAR_SUFFIX;
|
||||
break;
|
||||
default:
|
||||
this.urlSuffix = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.followRedirects(false);
|
||||
requestToPrepare.param(PRIVATE_DATA_API_AGGREGATE_DATA_CHARTFIELD_FIELD, period.toString());
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return PRIVATE_DATA_API_URL + config.getSolarId() + urlSuffix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
logger.debug("onComplete()");
|
||||
|
||||
if (!HttpStatus.Code.OK.equals(getCommunicationStatus().getHttpCode())) {
|
||||
updateListenerStatus();
|
||||
if (retries++ < MAX_RETRIES) {
|
||||
handler.getWebInterface().enqueueCommand(this);
|
||||
}
|
||||
} else {
|
||||
String json = getContentAsString(StandardCharsets.UTF_8);
|
||||
if (json != null) {
|
||||
logger.debug("JSON String: {}", json);
|
||||
AggregateDataResponsePrivateApi jsonObject = fromJson(json, AggregateDataResponsePrivateApi.class);
|
||||
if (jsonObject != null) {
|
||||
handler.updateChannelStatus(transformer.transform(jsonObject, period));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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.solaredge.internal.command;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.solaredge.internal.callback.AbstractCommandCallback;
|
||||
import org.openhab.binding.solaredge.internal.handler.SolarEdgeHandler;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePublicApi;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponseTransformerPublicApi;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregatePeriod;
|
||||
|
||||
/**
|
||||
* command that retrieves status values for aggregate data channels via public API
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AggregateDataUpdatePublicApi extends AbstractCommandCallback implements SolarEdgeCommand {
|
||||
|
||||
/**
|
||||
* the solaredge handler
|
||||
*/
|
||||
private final SolarEdgeHandler handler;
|
||||
private final AggregateDataResponseTransformerPublicApi transformer;
|
||||
|
||||
/**
|
||||
* data aggregation level
|
||||
*/
|
||||
private final AggregatePeriod period;
|
||||
|
||||
/**
|
||||
* date format which is expected by the API
|
||||
*/
|
||||
private final SimpleDateFormat dateFormat;
|
||||
private int retries = 0;
|
||||
|
||||
/**
|
||||
* the constructor
|
||||
*
|
||||
* @param handler
|
||||
* @param period
|
||||
*/
|
||||
public AggregateDataUpdatePublicApi(SolarEdgeHandler handler, AggregatePeriod period) {
|
||||
super(handler.getConfiguration());
|
||||
this.dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
this.handler = handler;
|
||||
this.transformer = new AggregateDataResponseTransformerPublicApi(handler);
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.followRedirects(false);
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
|
||||
String currentDate = dateFormat.format(Calendar.getInstance().getTime());
|
||||
|
||||
requestToPrepare.param(PUBLIC_DATA_API_TIME_UNIT_FIELD, period.toString());
|
||||
requestToPrepare.param(PUBLIC_DATA_API_START_TIME_FIELD, currentDate + " " + BEGIN_OF_DAY_TIME);
|
||||
requestToPrepare.param(PUBLIC_DATA_API_END_TIME_FIELD, currentDate + " " + END_OF_DAY_TIME);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return PUBLIC_DATA_API_URL + config.getSolarId() + PUBLIC_DATA_API_URL_AGGREGATE_DATA_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
logger.debug("onComplete()");
|
||||
|
||||
if (!HttpStatus.Code.OK.equals(getCommunicationStatus().getHttpCode())) {
|
||||
updateListenerStatus();
|
||||
if (retries++ < MAX_RETRIES) {
|
||||
handler.getWebInterface().enqueueCommand(this);
|
||||
}
|
||||
} else {
|
||||
String json = getContentAsString(StandardCharsets.UTF_8);
|
||||
if (json != null) {
|
||||
logger.debug("JSON String: {}", json);
|
||||
AggregateDataResponsePublicApi jsonObject = fromJson(json, AggregateDataResponsePublicApi.class);
|
||||
if (jsonObject != null) {
|
||||
handler.updateChannelStatus(transformer.transform(jsonObject, period));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.solaredge.internal.command;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.solaredge.internal.callback.AbstractCommandCallback;
|
||||
import org.openhab.binding.solaredge.internal.handler.SolarEdgeHandler;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponseMeterless;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponseTransformer;
|
||||
|
||||
/**
|
||||
* command that retrieves status values for live data channels via public API
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LiveDataUpdateMeterless extends AbstractCommandCallback implements SolarEdgeCommand {
|
||||
|
||||
private final SolarEdgeHandler handler;
|
||||
private final LiveDataResponseTransformer transformer;
|
||||
private int retries = 0;
|
||||
|
||||
public LiveDataUpdateMeterless(SolarEdgeHandler handler) {
|
||||
super(handler.getConfiguration());
|
||||
this.handler = handler;
|
||||
this.transformer = new LiveDataResponseTransformer(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.followRedirects(false);
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return PUBLIC_DATA_API_URL + config.getSolarId() + PUBLIC_DATA_API_URL_LIVE_DATA_METERLESS_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
logger.debug("onComplete()");
|
||||
|
||||
if (!HttpStatus.Code.OK.equals(getCommunicationStatus().getHttpCode())) {
|
||||
updateListenerStatus();
|
||||
if (retries++ < MAX_RETRIES) {
|
||||
handler.getWebInterface().enqueueCommand(this);
|
||||
}
|
||||
} else {
|
||||
String json = getContentAsString(StandardCharsets.UTF_8);
|
||||
if (json != null) {
|
||||
logger.debug("JSON String: {}", json);
|
||||
LiveDataResponseMeterless jsonObject = fromJson(json, LiveDataResponseMeterless.class);
|
||||
if (jsonObject != null) {
|
||||
handler.updateChannelStatus(transformer.transform(jsonObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.solaredge.internal.command;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.solaredge.internal.callback.AbstractCommandCallback;
|
||||
import org.openhab.binding.solaredge.internal.handler.SolarEdgeHandler;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponse;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponseTransformer;
|
||||
|
||||
/**
|
||||
* command that retrieves status values for live data channels via private API
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LiveDataUpdatePrivateApi extends AbstractCommandCallback implements SolarEdgeCommand {
|
||||
|
||||
private final SolarEdgeHandler handler;
|
||||
private final LiveDataResponseTransformer transformer;
|
||||
private int retries = 0;
|
||||
|
||||
public LiveDataUpdatePrivateApi(SolarEdgeHandler handler) {
|
||||
super(handler.getConfiguration());
|
||||
this.handler = handler;
|
||||
this.transformer = new LiveDataResponseTransformer(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.followRedirects(false);
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return PRIVATE_DATA_API_URL + config.getSolarId() + PRIVATE_DATA_API_URL_LIVE_DATA_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
logger.debug("onComplete()");
|
||||
|
||||
if (!HttpStatus.Code.OK.equals(getCommunicationStatus().getHttpCode())) {
|
||||
updateListenerStatus();
|
||||
if (retries++ < MAX_RETRIES) {
|
||||
handler.getWebInterface().enqueueCommand(this);
|
||||
}
|
||||
} else {
|
||||
String json = getContentAsString(StandardCharsets.UTF_8);
|
||||
if (json != null) {
|
||||
logger.debug("JSON String: {}", json);
|
||||
LiveDataResponse jsonObject = fromJson(json, LiveDataResponse.class);
|
||||
if (jsonObject != null) {
|
||||
handler.updateChannelStatus(transformer.transform(jsonObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.solaredge.internal.command;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.openhab.binding.solaredge.internal.callback.AbstractCommandCallback;
|
||||
import org.openhab.binding.solaredge.internal.handler.SolarEdgeHandler;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponse;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponseTransformer;
|
||||
|
||||
/**
|
||||
* command that retrieves status values for live data channels via public API
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LiveDataUpdatePublicApi extends AbstractCommandCallback implements SolarEdgeCommand {
|
||||
|
||||
private final SolarEdgeHandler handler;
|
||||
private final LiveDataResponseTransformer transformer;
|
||||
private int retries = 0;
|
||||
|
||||
public LiveDataUpdatePublicApi(SolarEdgeHandler handler) {
|
||||
super(handler.getConfiguration());
|
||||
this.handler = handler;
|
||||
this.transformer = new LiveDataResponseTransformer(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
requestToPrepare.followRedirects(false);
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return PUBLIC_DATA_API_URL + config.getSolarId() + PUBLIC_DATA_API_URL_LIVE_DATA_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
logger.debug("onComplete()");
|
||||
if (!HttpStatus.Code.OK.equals(getCommunicationStatus().getHttpCode())) {
|
||||
updateListenerStatus();
|
||||
if (retries++ < MAX_RETRIES) {
|
||||
handler.getWebInterface().enqueueCommand(this);
|
||||
}
|
||||
} else {
|
||||
String json = getContentAsString(StandardCharsets.UTF_8);
|
||||
if (json != null) {
|
||||
logger.debug("JSON String: {}", json);
|
||||
LiveDataResponse jsonObject = fromJson(json, LiveDataResponse.class);
|
||||
if (jsonObject != null) {
|
||||
handler.updateChannelStatus(transformer.transform(jsonObject));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.solaredge.internal.command;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.solaredge.internal.callback.AbstractCommandCallback;
|
||||
import org.openhab.binding.solaredge.internal.connector.StatusUpdateListener;
|
||||
import org.openhab.binding.solaredge.internal.handler.SolarEdgeHandler;
|
||||
|
||||
/**
|
||||
* checks validity of the token by accessing the webinterface
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PrivateApiTokenCheck extends AbstractCommandCallback implements SolarEdgeCommand {
|
||||
|
||||
public PrivateApiTokenCheck(SolarEdgeHandler handler, StatusUpdateListener listener) {
|
||||
super(handler.getConfiguration(), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
// as a token is used no real login is to be done here. It is just checked if a protected page can be retrieved
|
||||
// and therefore the token is valid.
|
||||
requestToPrepare.followRedirects(false);
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return PRIVATE_DATA_API_URL + config.getSolarId() + PRIVATE_DATA_API_URL_LIVE_DATA_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
updateListenerStatus();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.solaredge.internal.command;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.binding.solaredge.internal.callback.AbstractCommandCallback;
|
||||
import org.openhab.binding.solaredge.internal.connector.StatusUpdateListener;
|
||||
import org.openhab.binding.solaredge.internal.handler.SolarEdgeHandler;
|
||||
|
||||
/**
|
||||
* checks validity of the api key by accessing the webinterface
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PublicApiKeyCheck extends AbstractCommandCallback implements SolarEdgeCommand {
|
||||
|
||||
public PublicApiKeyCheck(SolarEdgeHandler handler, StatusUpdateListener listener) {
|
||||
super(handler.getConfiguration(), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Request prepareRequest(Request requestToPrepare) {
|
||||
// as a key is used no real login is to be done here. It is just checked if a protected page can be retrieved
|
||||
// and therefore the key is valid.
|
||||
requestToPrepare.followRedirects(false);
|
||||
requestToPrepare.method(HttpMethod.GET);
|
||||
|
||||
return requestToPrepare;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getURL() {
|
||||
return PUBLIC_DATA_API_URL + config.getSolarId() + PUBLIC_DATA_API_URL_LIVE_DATA_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(@Nullable Result result) {
|
||||
updateListenerStatus();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.solaredge.internal.command;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Response.CompleteListener;
|
||||
import org.eclipse.jetty.client.api.Response.ContentListener;
|
||||
import org.eclipse.jetty.client.api.Response.FailureListener;
|
||||
import org.eclipse.jetty.client.api.Response.SuccessListener;
|
||||
import org.openhab.binding.solaredge.internal.connector.StatusUpdateListener;
|
||||
|
||||
/**
|
||||
* public interface for all commands
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SolarEdgeCommand extends SuccessListener, FailureListener, ContentListener, CompleteListener {
|
||||
|
||||
int MAX_RETRIES = 5;
|
||||
|
||||
/**
|
||||
* this method is to be called by the UplinkWebinterface class
|
||||
*
|
||||
* @param asyncclient
|
||||
*/
|
||||
void performAction(HttpClient asyncclient);
|
||||
|
||||
/**
|
||||
* updates the listener's status
|
||||
*
|
||||
*/
|
||||
void updateListenerStatus();
|
||||
|
||||
/**
|
||||
* register a listener
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
void setListener(StatusUpdateListener listener);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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.solaredge.internal.config;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Bean holding configuration data according to bridge.xml
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SolarEdgeConfiguration {
|
||||
|
||||
private @Nullable String tokenOrApiKey;
|
||||
private @Nullable String solarId;
|
||||
|
||||
private boolean meterInstalled = false;
|
||||
private boolean usePrivateApi = false;
|
||||
|
||||
private Integer asyncTimeout = 120;
|
||||
private Integer syncTimeout = 120;
|
||||
private Integer liveDataPollingInterval = 10;
|
||||
private Integer aggregateDataPollingInterval = 60;
|
||||
|
||||
public @Nullable String getTokenOrApiKey() {
|
||||
return tokenOrApiKey;
|
||||
}
|
||||
|
||||
public void setTokenOrApiKey(String tokenOrApiKey) {
|
||||
this.tokenOrApiKey = tokenOrApiKey;
|
||||
}
|
||||
|
||||
public @Nullable String getSolarId() {
|
||||
return solarId;
|
||||
}
|
||||
|
||||
public void setSolarId(String solarId) {
|
||||
this.solarId = solarId;
|
||||
}
|
||||
|
||||
public Integer getAsyncTimeout() {
|
||||
return asyncTimeout;
|
||||
}
|
||||
|
||||
public void setAsyncTimeout(Integer asyncTimeout) {
|
||||
this.asyncTimeout = asyncTimeout;
|
||||
}
|
||||
|
||||
public Integer getSyncTimeout() {
|
||||
return syncTimeout;
|
||||
}
|
||||
|
||||
public void setSyncTimeout(Integer syncTimeout) {
|
||||
this.syncTimeout = syncTimeout;
|
||||
}
|
||||
|
||||
public Integer getLiveDataPollingInterval() {
|
||||
return liveDataPollingInterval;
|
||||
}
|
||||
|
||||
public void setLiveDataPollingInterval(Integer liveDataPollingInterval) {
|
||||
this.liveDataPollingInterval = liveDataPollingInterval;
|
||||
}
|
||||
|
||||
public Integer getAggregateDataPollingInterval() {
|
||||
return aggregateDataPollingInterval;
|
||||
}
|
||||
|
||||
public void setAggregateDataPollingInterval(Integer aggregateDataPollingInterval) {
|
||||
this.aggregateDataPollingInterval = aggregateDataPollingInterval;
|
||||
}
|
||||
|
||||
public boolean isMeterInstalled() {
|
||||
return meterInstalled;
|
||||
}
|
||||
|
||||
public void setMeterInstalled(boolean meterInstalled) {
|
||||
this.meterInstalled = meterInstalled;
|
||||
}
|
||||
|
||||
public boolean isUsePrivateApi() {
|
||||
return usePrivateApi;
|
||||
}
|
||||
|
||||
public void setUsePrivateApi(boolean usePrivateApi) {
|
||||
this.usePrivateApi = usePrivateApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this).append("tokenOrApiKey", getTokenOrApiKey()).append("solarId", getSolarId())
|
||||
.append("meterInstalled", isMeterInstalled()).append("usePrivateApi", isUsePrivateApi())
|
||||
.append("live data pollingInterval", getLiveDataPollingInterval())
|
||||
.append("aggregate data pollingInterval", getAggregateDataPollingInterval())
|
||||
.append("asyncTimeout", getAsyncTimeout()).append("syncTimeout", getSyncTimeout()).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.solaredge.internal.connector;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.http.HttpStatus.Code;
|
||||
|
||||
/**
|
||||
* this class contains the HTTP status of the communication and an optional exception that might occoured during
|
||||
* communication
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CommunicationStatus {
|
||||
|
||||
private @Nullable Code httpCode;
|
||||
private @Nullable Exception error;
|
||||
|
||||
public final Code getHttpCode() {
|
||||
Code httpCode = this.httpCode;
|
||||
return httpCode == null ? Code.INTERNAL_SERVER_ERROR : httpCode;
|
||||
}
|
||||
|
||||
public final void setHttpCode(Code httpCode) {
|
||||
this.httpCode = httpCode;
|
||||
}
|
||||
|
||||
public final @Nullable Exception getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public final void setError(Exception error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public final String getMessage() {
|
||||
Code httpCode = this.httpCode;
|
||||
Exception error = this.error;
|
||||
if (error != null && error.getMessage() != null && !error.getMessage().isEmpty()) {
|
||||
return error.getMessage();
|
||||
} else if (httpCode != null && httpCode.getMessage() != null && !httpCode.getMessage().isEmpty()) {
|
||||
return httpCode.getMessage();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.solaredge.internal.connector;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* callback interface to update the status of the {@link WebInterface}
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface StatusUpdateListener {
|
||||
|
||||
void update(CommunicationStatus status);
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* 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.solaredge.internal.connector;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.openhab.binding.solaredge.internal.AtomicReferenceTrait;
|
||||
import org.openhab.binding.solaredge.internal.command.PrivateApiTokenCheck;
|
||||
import org.openhab.binding.solaredge.internal.command.PublicApiKeyCheck;
|
||||
import org.openhab.binding.solaredge.internal.command.SolarEdgeCommand;
|
||||
import org.openhab.binding.solaredge.internal.config.SolarEdgeConfiguration;
|
||||
import org.openhab.binding.solaredge.internal.handler.SolarEdgeHandler;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The connector is responsible for communication with the solaredge webportal
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WebInterface implements AtomicReferenceTrait {
|
||||
|
||||
private static final int API_KEY_THRESHOLD = 40;
|
||||
private static final int TOKEN_THRESHOLD = 80;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(WebInterface.class);
|
||||
|
||||
/**
|
||||
* Configuration
|
||||
*/
|
||||
private SolarEdgeConfiguration config;
|
||||
|
||||
/**
|
||||
* handler for updating thing status
|
||||
*/
|
||||
private final SolarEdgeHandler handler;
|
||||
|
||||
/**
|
||||
* holds authentication status
|
||||
*/
|
||||
private boolean authenticated = false;
|
||||
|
||||
/**
|
||||
* HTTP client for asynchronous calls
|
||||
*/
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/**
|
||||
* the scheduler which periodically sends web requests to the solaredge API. Should be initiated with the thing's
|
||||
* existing scheduler instance.
|
||||
*/
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
/**
|
||||
* request executor
|
||||
*/
|
||||
private final WebRequestExecutor requestExecutor;
|
||||
|
||||
/**
|
||||
* periodic request executor job
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> requestExecutorJobReference;
|
||||
|
||||
/**
|
||||
* this class is responsible for executing periodic web requests. This ensures that only one request is executed at
|
||||
* the same time and there will be a guaranteed minimum delay between subsequent requests.
|
||||
*
|
||||
* @author afriese - initial contribution
|
||||
*/
|
||||
private class WebRequestExecutor implements Runnable {
|
||||
|
||||
/**
|
||||
* queue which holds the commands to execute
|
||||
*/
|
||||
private final Queue<SolarEdgeCommand> commandQueue;
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*/
|
||||
WebRequestExecutor() {
|
||||
this.commandQueue = new BlockingArrayQueue<>(WEB_REQUEST_QUEUE_MAX_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* puts a command into the queue
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
void enqueue(SolarEdgeCommand command) {
|
||||
try {
|
||||
commandQueue.add(command);
|
||||
} catch (IllegalStateException ex) {
|
||||
if (commandQueue.size() >= WEB_REQUEST_QUEUE_MAX_SIZE) {
|
||||
logger.debug(
|
||||
"Could not add command to command queue because queue is already full. Maybe SolarEdge is down?");
|
||||
} else {
|
||||
logger.warn("Could not add command to queue - IllegalStateException");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* executes the web request
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isAuthenticated()) {
|
||||
authenticate();
|
||||
}
|
||||
|
||||
else if (isAuthenticated() && !commandQueue.isEmpty()) {
|
||||
StatusUpdateListener statusUpdater = new StatusUpdateListener() {
|
||||
@Override
|
||||
public void update(CommunicationStatus status) {
|
||||
switch (status.getHttpCode()) {
|
||||
case SERVICE_UNAVAILABLE:
|
||||
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
status.getMessage());
|
||||
setAuthenticated(false);
|
||||
break;
|
||||
case OK:
|
||||
// no action needed as the thing is already online.
|
||||
break;
|
||||
default:
|
||||
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
status.getMessage());
|
||||
setAuthenticated(false);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SolarEdgeCommand command = commandQueue.poll();
|
||||
command.setListener(statusUpdater);
|
||||
command.performAction(httpClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor to set up interface
|
||||
*
|
||||
* @param config Bridge configuration
|
||||
*/
|
||||
public WebInterface(ScheduledExecutorService scheduler, SolarEdgeHandler handler, HttpClient httpClient) {
|
||||
this.config = handler.getConfiguration();
|
||||
this.handler = handler;
|
||||
this.scheduler = scheduler;
|
||||
this.httpClient = httpClient;
|
||||
this.requestExecutor = new WebRequestExecutor();
|
||||
this.requestExecutorJobReference = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
this.config = handler.getConfiguration();
|
||||
setAuthenticated(false);
|
||||
updateJobReference(requestExecutorJobReference, scheduler.scheduleWithFixedDelay(requestExecutor,
|
||||
WEB_REQUEST_INITIAL_DELAY, WEB_REQUEST_INTERVAL, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
/**
|
||||
* queues any command for execution
|
||||
*
|
||||
* @param command
|
||||
*/
|
||||
public void enqueueCommand(SolarEdgeCommand command) {
|
||||
requestExecutor.enqueue(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticates with the Solaredge WEB interface
|
||||
*
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
private synchronized void authenticate() {
|
||||
setAuthenticated(false);
|
||||
|
||||
if (preCheck()) {
|
||||
SolarEdgeCommand tokenCheckCommand;
|
||||
|
||||
StatusUpdateListener tokenCheckListener = new StatusUpdateListener() {
|
||||
|
||||
@Override
|
||||
public void update(CommunicationStatus status) {
|
||||
String errorMessageCodeFound;
|
||||
String errorMessgaeCodeForbidden;
|
||||
if (config.isUsePrivateApi()) {
|
||||
errorMessageCodeFound = "login error with private API: invalid token";
|
||||
errorMessgaeCodeForbidden = "login error with private API: invalid solarId";
|
||||
} else {
|
||||
errorMessageCodeFound = "login error with public API: unknown error";
|
||||
errorMessgaeCodeForbidden = "login error with public API: invalid api key or solarId is not valid for this api key";
|
||||
}
|
||||
|
||||
switch (status.getHttpCode()) {
|
||||
case OK:
|
||||
handler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, "logged in");
|
||||
setAuthenticated(true);
|
||||
break;
|
||||
case FOUND:
|
||||
handler.setStatusInfo(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
errorMessageCodeFound);
|
||||
setAuthenticated(false);
|
||||
break;
|
||||
case FORBIDDEN:
|
||||
handler.setStatusInfo(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
errorMessgaeCodeForbidden);
|
||||
setAuthenticated(false);
|
||||
break;
|
||||
case SERVICE_UNAVAILABLE:
|
||||
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||
status.getMessage());
|
||||
setAuthenticated(false);
|
||||
break;
|
||||
default:
|
||||
handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
status.getMessage());
|
||||
setAuthenticated(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (config.isUsePrivateApi()) {
|
||||
tokenCheckCommand = new PrivateApiTokenCheck(handler, tokenCheckListener);
|
||||
} else {
|
||||
tokenCheckCommand = new PublicApiKeyCheck(handler, tokenCheckListener);
|
||||
}
|
||||
tokenCheckCommand.performAction(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* performs some pre cheks on configuration before attempting to login
|
||||
*
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
private boolean preCheck() {
|
||||
String preCheckStatusMessage = "";
|
||||
String localTokenOrApiKey = config.getTokenOrApiKey();
|
||||
String localSolarId = config.getSolarId();
|
||||
|
||||
if (localTokenOrApiKey == null || localTokenOrApiKey.isEmpty()) {
|
||||
preCheckStatusMessage = "please configure token/api_key first";
|
||||
} else if (localSolarId == null || localSolarId.isEmpty()) {
|
||||
preCheckStatusMessage = "please configure solarId first";
|
||||
} else if (config.isUsePrivateApi() && localTokenOrApiKey.length() < TOKEN_THRESHOLD) {
|
||||
preCheckStatusMessage = "you will have to use a 'token' and not an 'api key' when using private API";
|
||||
} else if (!config.isUsePrivateApi() && localTokenOrApiKey.length() > API_KEY_THRESHOLD) {
|
||||
preCheckStatusMessage = "you will have to use an 'api key' and not a 'token' when using public API";
|
||||
} else if (!config.isUsePrivateApi() && calcRequestsPerDay() > WEB_REQUEST_PUBLIC_API_DAY_LIMIT) {
|
||||
preCheckStatusMessage = "daily request limit (" + WEB_REQUEST_PUBLIC_API_DAY_LIMIT + ") exceeded: "
|
||||
+ calcRequestsPerDay();
|
||||
} else if (config.isUsePrivateApi() && !config.isMeterInstalled()) {
|
||||
preCheckStatusMessage = "a meter must be present in order to use the private API";
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.handler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, preCheckStatusMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates requests per day. just an internal helper
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private long calcRequestsPerDay() {
|
||||
return MINUTES_PER_DAY / this.config.getLiveDataPollingInterval()
|
||||
+ 4 * MINUTES_PER_DAY / this.config.getAggregateDataPollingInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
* will be called by the ThingHandler to abort periodic jobs.
|
||||
*/
|
||||
public void dispose() {
|
||||
logger.debug("Webinterface disposed.");
|
||||
cancelJobReference(requestExecutorJobReference);
|
||||
setAuthenticated(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns authentication status.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private boolean isAuthenticated() {
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
private void setAuthenticated(boolean authenticated) {
|
||||
this.authenticated = authenticated;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.solaredge.internal.handler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Channel;
|
||||
|
||||
/**
|
||||
* this interface provides all methods which deal with channels
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ChannelProvider {
|
||||
|
||||
/**
|
||||
* returns a list containing all channels
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<Channel> getChannels();
|
||||
|
||||
@Nullable
|
||||
Channel getChannel(String groupId, String channelId);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.solaredge.internal.handler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelGroupUID;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* generic thing handler for solaredge
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GenericSolarEdgeHandler extends SolarEdgeBaseHandler {
|
||||
|
||||
public GenericSolarEdgeHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing, httpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Channel> getChannels() {
|
||||
return getThing().getChannels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Channel getChannel(String groupId, String channelId) {
|
||||
ThingUID thingUID = this.getThing().getUID();
|
||||
ChannelGroupUID channelGroupUID = new ChannelGroupUID(thingUID, groupId);
|
||||
Channel channel = getThing().getChannel(new ChannelUID(channelGroupUID, channelId));
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.solaredge.internal.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.solaredge.internal.command.AggregateDataUpdatePrivateApi;
|
||||
import org.openhab.binding.solaredge.internal.command.AggregateDataUpdatePublicApi;
|
||||
import org.openhab.binding.solaredge.internal.command.SolarEdgeCommand;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregatePeriod;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Polling worker class. This is responsible for periodic polling of sensor data.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SolarEdgeAggregateDataPolling implements Runnable {
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* Handler for delegation to callbacks.
|
||||
*/
|
||||
private final SolarEdgeHandler handler;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param handler handler which handles results of polling
|
||||
*/
|
||||
public SolarEdgeAggregateDataPolling(SolarEdgeHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll the SolarEdge Webservice one time per call.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
// if no meter is present all data will be fetched by the 'LiveDataUpdateMeterless'
|
||||
if (handler.getConfiguration().isMeterInstalled()) {
|
||||
logger.debug("polling SolarEdge aggregate data {}", handler.getConfiguration());
|
||||
|
||||
List<SolarEdgeCommand> commands = new ArrayList<>();
|
||||
|
||||
if (handler.getConfiguration().isUsePrivateApi()) {
|
||||
commands.add(new AggregateDataUpdatePrivateApi(handler, AggregatePeriod.DAY));
|
||||
commands.add(new AggregateDataUpdatePrivateApi(handler, AggregatePeriod.WEEK));
|
||||
commands.add(new AggregateDataUpdatePrivateApi(handler, AggregatePeriod.MONTH));
|
||||
commands.add(new AggregateDataUpdatePrivateApi(handler, AggregatePeriod.YEAR));
|
||||
} else {
|
||||
commands.add(new AggregateDataUpdatePublicApi(handler, AggregatePeriod.DAY));
|
||||
commands.add(new AggregateDataUpdatePublicApi(handler, AggregatePeriod.WEEK));
|
||||
commands.add(new AggregateDataUpdatePublicApi(handler, AggregatePeriod.MONTH));
|
||||
commands.add(new AggregateDataUpdatePublicApi(handler, AggregatePeriod.YEAR));
|
||||
}
|
||||
|
||||
for (SolarEdgeCommand command : commands) {
|
||||
handler.getWebInterface().enqueueCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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.solaredge.internal.handler;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.solaredge.internal.AtomicReferenceTrait;
|
||||
import org.openhab.binding.solaredge.internal.config.SolarEdgeConfiguration;
|
||||
import org.openhab.binding.solaredge.internal.connector.WebInterface;
|
||||
import org.openhab.core.thing.Channel;
|
||||
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.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link SolarEdgeBaseHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class SolarEdgeBaseHandler extends BaseThingHandler implements SolarEdgeHandler, AtomicReferenceTrait {
|
||||
private final Logger logger = LoggerFactory.getLogger(SolarEdgeBaseHandler.class);
|
||||
|
||||
private final long LIVE_POLLING_INITIAL_DELAY = 1;
|
||||
private final long AGGREGATE_POLLING_INITIAL_DELAY = 2;
|
||||
|
||||
/**
|
||||
* Interface object for querying the Solaredge web interface
|
||||
*/
|
||||
private WebInterface webInterface;
|
||||
|
||||
/**
|
||||
* Schedule for polling live data
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> liveDataPollingJobReference;
|
||||
|
||||
/**
|
||||
* Schedule for polling aggregate data
|
||||
*/
|
||||
private final AtomicReference<@Nullable Future<?>> aggregateDataPollingJobReference;
|
||||
|
||||
public SolarEdgeBaseHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.webInterface = new WebInterface(scheduler, this, httpClient);
|
||||
this.liveDataPollingJobReference = new AtomicReference<>(null);
|
||||
this.aggregateDataPollingJobReference = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("command for {}: {}", channelUID, command);
|
||||
// write access is not supported.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("About to initialize SolarEdge");
|
||||
SolarEdgeConfiguration config = getConfiguration();
|
||||
logger.debug("Solaredge initialized with configuration: {}", config);
|
||||
|
||||
startPolling();
|
||||
webInterface.start();
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "waiting for web api login");
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the polling.
|
||||
*/
|
||||
private void startPolling() {
|
||||
updateJobReference(liveDataPollingJobReference,
|
||||
scheduler.scheduleWithFixedDelay(new SolarEdgeLiveDataPolling(this), LIVE_POLLING_INITIAL_DELAY,
|
||||
getConfiguration().getLiveDataPollingInterval(), TimeUnit.MINUTES));
|
||||
|
||||
updateJobReference(aggregateDataPollingJobReference,
|
||||
scheduler.scheduleWithFixedDelay(new SolarEdgeAggregateDataPolling(this),
|
||||
AGGREGATE_POLLING_INITIAL_DELAY, getConfiguration().getAggregateDataPollingInterval(),
|
||||
TimeUnit.MINUTES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the bridge.
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Handler disposed.");
|
||||
|
||||
cancelJobReference(liveDataPollingJobReference);
|
||||
cancelJobReference(aggregateDataPollingJobReference);
|
||||
|
||||
webInterface.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebInterface getWebInterface() {
|
||||
return webInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* will update all channels provided in the map
|
||||
*/
|
||||
@Override
|
||||
public void updateChannelStatus(Map<Channel, @Nullable State> values) {
|
||||
logger.debug("Handling channel update.");
|
||||
|
||||
for (Channel channel : values.keySet()) {
|
||||
if (getChannels().contains(channel)) {
|
||||
State value = values.get(channel);
|
||||
if (value != null) {
|
||||
logger.debug("Channel is to be updated: {}: {}", channel.getUID().getAsString(), value);
|
||||
updateState(channel.getUID(), value);
|
||||
} else {
|
||||
logger.debug("Value is null or not provided by solaredge (channel: {})",
|
||||
channel.getUID().getAsString());
|
||||
updateState(channel.getUID(), UnDefType.UNDEF);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Could not identify channel: {} for model {}", channel.getUID().getAsString(),
|
||||
getThing().getThingTypeUID().getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatusInfo(ThingStatus status, ThingStatusDetail statusDetail, String description) {
|
||||
super.updateStatus(status, statusDetail, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SolarEdgeConfiguration getConfiguration() {
|
||||
return this.getConfigAs(SolarEdgeConfiguration.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.solaredge.internal.handler;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.solaredge.internal.config.SolarEdgeConfiguration;
|
||||
import org.openhab.binding.solaredge.internal.connector.WebInterface;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* public interface of the {@link SolarEdgeHandler}
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface SolarEdgeHandler extends ThingHandler, ChannelProvider {
|
||||
/**
|
||||
* Called from {@link WebInterface#authenticate()} to update
|
||||
* the thing status because updateStatus is protected.
|
||||
*
|
||||
* @param status Bridge status
|
||||
* @param statusDetail Bridge status detail
|
||||
* @param description Bridge status description
|
||||
*/
|
||||
void setStatusInfo(ThingStatus status, ThingStatusDetail statusDetail, String description);
|
||||
|
||||
/**
|
||||
* Provides the web interface object.
|
||||
*
|
||||
* @return The web interface object
|
||||
*/
|
||||
WebInterface getWebInterface();
|
||||
|
||||
/**
|
||||
* method which updates the channels.
|
||||
*
|
||||
* @param values key-value list where key is the channel
|
||||
*/
|
||||
void updateChannelStatus(Map<Channel, State> values);
|
||||
|
||||
/**
|
||||
* return the binding's configuration
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
SolarEdgeConfiguration getConfiguration();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.solaredge.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.solaredge.internal.command.LiveDataUpdateMeterless;
|
||||
import org.openhab.binding.solaredge.internal.command.LiveDataUpdatePrivateApi;
|
||||
import org.openhab.binding.solaredge.internal.command.LiveDataUpdatePublicApi;
|
||||
import org.openhab.binding.solaredge.internal.command.SolarEdgeCommand;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Polling worker class. This is responsible for periodic polling of sensor data.
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SolarEdgeLiveDataPolling implements Runnable {
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* Handler for delegation to callbacks.
|
||||
*/
|
||||
private final SolarEdgeHandler handler;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param handler handler which handles results of polling
|
||||
*/
|
||||
public SolarEdgeLiveDataPolling(SolarEdgeHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll the SolarEdge Webservice one time per call.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
logger.debug("polling SolarEdge live data {}", handler.getConfiguration());
|
||||
|
||||
SolarEdgeCommand ldu;
|
||||
|
||||
if (handler.getConfiguration().isUsePrivateApi()) {
|
||||
ldu = new LiveDataUpdatePrivateApi(handler);
|
||||
} else {
|
||||
if (handler.getConfiguration().isMeterInstalled()) {
|
||||
ldu = new LiveDataUpdatePublicApi(handler);
|
||||
} else {
|
||||
ldu = new LiveDataUpdateMeterless(handler);
|
||||
}
|
||||
}
|
||||
|
||||
handler.getWebInterface().enqueueCommand(ldu);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* 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.solaredge.internal.model;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.Quantity;
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Energy;
|
||||
import javax.measure.quantity.Power;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePrivateApi.Value;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePrivateApi.ValueAndPercent;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePublicApi.MeterTelemetry;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SmartHomeUnits;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* common interface for all data response classes
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class AbstractDataResponseTransformer {
|
||||
static final String UNIT_WH = "Wh";
|
||||
static final String UNIT_KWH = "kWh";
|
||||
static final String UNIT_MWH = "MWh";
|
||||
static final String UNIT_W = "W";
|
||||
static final String UNIT_KW = "kW";
|
||||
static final String UNIT_MW = "MW";
|
||||
|
||||
/**
|
||||
* logger
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstractDataResponseTransformer.class);
|
||||
|
||||
/**
|
||||
* determines the unit, also handles wrong spelling of kWh (which is spelled with capital K by API)
|
||||
*
|
||||
* @param unit
|
||||
* @return
|
||||
*/
|
||||
private final @Nullable Unit<Energy> determineEnergyUnit(@Nullable String unit) {
|
||||
if (unit != null) {
|
||||
if (unit.equals(UNIT_WH)) {
|
||||
return SmartHomeUnits.WATT_HOUR;
|
||||
} else if (unit.toLowerCase().equals(UNIT_KWH.toLowerCase())) {
|
||||
return MetricPrefix.KILO(SmartHomeUnits.WATT_HOUR);
|
||||
} else if (unit.equals(UNIT_MWH)) {
|
||||
return MetricPrefix.MEGA(SmartHomeUnits.WATT_HOUR);
|
||||
}
|
||||
}
|
||||
logger.debug("Could not determine energy unit: '{}'", unit);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* determines the unit, also handles wrong spelling of kW (which is spelled with capital K by API)
|
||||
*
|
||||
* @param unit
|
||||
* @return
|
||||
*/
|
||||
private final @Nullable Unit<Power> determinePowerUnit(@Nullable String unit) {
|
||||
if (unit != null) {
|
||||
if (unit.equals(UNIT_W)) {
|
||||
return SmartHomeUnits.WATT;
|
||||
} else if (unit.toLowerCase().equals(UNIT_KW.toLowerCase())) {
|
||||
return MetricPrefix.KILO(SmartHomeUnits.WATT);
|
||||
} else if (unit.equals(UNIT_MW)) {
|
||||
return MetricPrefix.MEGA(SmartHomeUnits.WATT);
|
||||
}
|
||||
}
|
||||
logger.debug("Could not determine power unit: '{}'", unit);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the value to QuantityType and puts it into the targetMap. If no value or unit is provided
|
||||
* UnDefType.UNDEF will be used
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param value the value to convert
|
||||
* @param unit the unit to be used
|
||||
*/
|
||||
private final <T extends Quantity<T>> void putQuantityType(Map<Channel, State> targetMap, Channel channel,
|
||||
@Nullable Double value, @Nullable Unit<T> unit) {
|
||||
State result = UnDefType.UNDEF;
|
||||
|
||||
if (value != null && unit != null) {
|
||||
result = new QuantityType<>(value, unit);
|
||||
logger.debug("Channel {} transformed to QuantityType ({} {}) -> {}", channel.getUID().getId(), value, unit,
|
||||
result);
|
||||
} else {
|
||||
logger.debug("Channel {}: no value/unit provided", channel.getUID().getId());
|
||||
}
|
||||
targetMap.put(channel, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the value to QuantityType<Power> and puts it into the targetMap. If no value or unit is provided
|
||||
* UnDefType.UNDEF will be used
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param value the value to convert
|
||||
* @param unit as string
|
||||
*/
|
||||
protected final void putPowerType(Map<Channel, State> targetMap, @Nullable Channel channel, @Nullable Double value,
|
||||
@Nullable String unitAsString) {
|
||||
if (channel != null) {
|
||||
Unit<Power> unit = determinePowerUnit(unitAsString);
|
||||
putQuantityType(targetMap, channel, value, unit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the value to QuantityType<Energy> and puts it into the targetMap. If no value or unit is provided
|
||||
* UnDefType.UNDEF will be used
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param value the value to convert
|
||||
* @param unit as string
|
||||
*/
|
||||
protected final void putEnergyType(Map<Channel, State> targetMap, @Nullable Channel channel, @Nullable Double value,
|
||||
@Nullable String unitAsString) {
|
||||
if (channel != null) {
|
||||
Unit<Energy> unit = determineEnergyUnit(unitAsString);
|
||||
putQuantityType(targetMap, channel, value, unit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the value to QuantityType<Energy> and puts it into the targetMap. If no value or unit is provided
|
||||
* UnDefType.UNDEF will be used
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param value the value to convert
|
||||
*/
|
||||
protected final void putEnergyType(Map<Channel, State> targetMap, @Nullable Channel channel, Value value) {
|
||||
putEnergyType(targetMap, channel, value.value, value.unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the meter value to QuantityType<Energy> and puts it into the targetMap. If multiple meter value are
|
||||
* provided a sum will be calculated. If no
|
||||
* unit can be determined UnDefType.UNDEF will be used
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param values one or more meter values
|
||||
*/
|
||||
protected final void putEnergyType(Map<Channel, State> targetMap, @Nullable Channel channel, @Nullable String unit,
|
||||
MeterTelemetry... values) {
|
||||
double sum = 0.0;
|
||||
for (MeterTelemetry value : values) {
|
||||
if (value.value != null) {
|
||||
sum += value.value;
|
||||
}
|
||||
}
|
||||
putEnergyType(targetMap, channel, sum, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* put value as StringType into targetMap.
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param value the value
|
||||
*/
|
||||
protected final void putStringType(Map<Channel, State> targetMap, @Nullable Channel channel,
|
||||
@Nullable String value) {
|
||||
if (channel != null) {
|
||||
State result = UnDefType.UNDEF;
|
||||
|
||||
if (value != null) {
|
||||
result = new StringType(value);
|
||||
logger.debug("Channel {} transformed to StringType ({}) -> {}", channel.getUID().getId(), value,
|
||||
result);
|
||||
|
||||
} else {
|
||||
logger.debug("Channel {}: no value provided.", channel);
|
||||
}
|
||||
targetMap.put(channel, result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* put value as PercentType into targetMap.
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param value the value to convert
|
||||
* @param factor to be applied (usually 1 or 100)
|
||||
*/
|
||||
protected final void putPercentType(Map<Channel, State> targetMap, @Nullable Channel channel,
|
||||
@Nullable Double value, int factor) {
|
||||
if (channel != null) {
|
||||
State result = UnDefType.UNDEF;
|
||||
|
||||
if (value != null) {
|
||||
result = new QuantityType<>(value * factor, SmartHomeUnits.PERCENT);
|
||||
} else {
|
||||
logger.debug("Channel {}: no value provided.", channel.getUID().getAsString());
|
||||
}
|
||||
targetMap.put(channel, result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* put value as PercentType into targetMap.
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param value the value to convert
|
||||
*/
|
||||
protected final void putPercentType(Map<Channel, State> targetMap, @Nullable Channel channel,
|
||||
@Nullable Double value) {
|
||||
putPercentType(targetMap, channel, value, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* put value as PercentType into targetMap.
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param value the value to convert
|
||||
*/
|
||||
protected final void putPercentType(Map<Channel, State> targetMap, @Nullable Channel channel,
|
||||
ValueAndPercent value) {
|
||||
putPercentType(targetMap, channel, value.percentage, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates percentage and puts it into the targetmap
|
||||
*
|
||||
* @param targetMap result will be put into this map
|
||||
* @param channel channel to assign the value
|
||||
* @param dividendAsState
|
||||
* @param divisorAsState
|
||||
*/
|
||||
protected final void putPercentType(Map<Channel, State> targetMap, @Nullable Channel channel,
|
||||
@Nullable State dividendAsState, @Nullable State divisorAsState) {
|
||||
Double percent = null;
|
||||
|
||||
if (dividendAsState != null && divisorAsState != null) {
|
||||
DecimalType dividendAsDecimalType = dividendAsState.as(DecimalType.class);
|
||||
DecimalType divisorAsDecimalType = divisorAsState.as(DecimalType.class);
|
||||
|
||||
if (dividendAsDecimalType != null && divisorAsDecimalType != null) {
|
||||
double dividend = dividendAsDecimalType.doubleValue();
|
||||
double divisor = divisorAsDecimalType.doubleValue();
|
||||
if (dividend >= 0.0 && divisor > 0.0) {
|
||||
percent = dividend / divisor * 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
putPercentType(targetMap, channel, percent);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the aggregate period to the correpsonding channel group.
|
||||
*
|
||||
* @param period
|
||||
* @return
|
||||
*/
|
||||
protected String convertPeriodToGroup(AggregatePeriod period) {
|
||||
String group = "undefined";
|
||||
switch (period) {
|
||||
case DAY:
|
||||
group = CHANNEL_GROUP_AGGREGATE_DAY;
|
||||
break;
|
||||
case WEEK:
|
||||
group = CHANNEL_GROUP_AGGREGATE_WEEK;
|
||||
break;
|
||||
case MONTH:
|
||||
group = CHANNEL_GROUP_AGGREGATE_MONTH;
|
||||
break;
|
||||
case YEAR:
|
||||
group = CHANNEL_GROUP_AGGREGATE_YEAR;
|
||||
break;
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.solaredge.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* this abstract class is used as base for the specific aggregate response classes
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AggregateDataResponsePrivateApi {
|
||||
|
||||
public static class Value {
|
||||
public @Nullable Double value;
|
||||
public @Nullable String unit;
|
||||
}
|
||||
|
||||
public static class ValueAndPercent extends Value {
|
||||
public @Nullable Double percentage;
|
||||
}
|
||||
|
||||
public static class UtilizationMeasures {
|
||||
public @Nullable Value production;
|
||||
public @Nullable Value consumption;
|
||||
public @Nullable ValueAndPercent selfConsumptionForConsumption;
|
||||
public @Nullable ValueAndPercent batterySelfConsumption;
|
||||
@SerializedName("import")
|
||||
public @Nullable ValueAndPercent imported;
|
||||
public @Nullable ValueAndPercent export;
|
||||
}
|
||||
|
||||
private @Nullable UtilizationMeasures utilizationMeasures;
|
||||
|
||||
public final @Nullable UtilizationMeasures getUtilizationMeasures() {
|
||||
return utilizationMeasures;
|
||||
}
|
||||
|
||||
public final void setUtilizationMeasures(UtilizationMeasures utilizationMeasures) {
|
||||
this.utilizationMeasures = utilizationMeasures;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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.solaredge.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* this class is used to map the aggregate data response of the public API
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AggregateDataResponsePublicApi {
|
||||
|
||||
public static class MeterTelemetry {
|
||||
public @Nullable String date;
|
||||
public @Nullable Double value;
|
||||
}
|
||||
|
||||
public static class MeterTelemetries {
|
||||
public @Nullable String type;
|
||||
public @Nullable List<MeterTelemetry> values;
|
||||
}
|
||||
|
||||
public static class EnergyDetails {
|
||||
public @Nullable AggregatePeriod timeUnit;
|
||||
public @Nullable String unit;
|
||||
public @Nullable List<MeterTelemetries> meters;
|
||||
}
|
||||
|
||||
private @Nullable EnergyDetails energyDetails;
|
||||
|
||||
public @Nullable EnergyDetails getEnergyDetails() {
|
||||
return energyDetails;
|
||||
}
|
||||
|
||||
public void setEnergyDetails(EnergyDetails energyDetails) {
|
||||
this.energyDetails = energyDetails;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.solaredge.internal.model;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.solaredge.internal.handler.ChannelProvider;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePrivateApi.UtilizationMeasures;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePrivateApi.Value;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePrivateApi.ValueAndPercent;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* transforms the http response into the openhab datamodel (instances of State)
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AggregateDataResponseTransformerPrivateApi extends AbstractDataResponseTransformer {
|
||||
private final ChannelProvider channelProvider;
|
||||
|
||||
public AggregateDataResponseTransformerPrivateApi(ChannelProvider channelProvider) {
|
||||
this.channelProvider = channelProvider;
|
||||
}
|
||||
|
||||
public Map<Channel, State> transform(AggregateDataResponsePrivateApi response, AggregatePeriod period) {
|
||||
Map<Channel, State> result = new HashMap<>(20);
|
||||
UtilizationMeasures utilizationMeasures = response.getUtilizationMeasures();
|
||||
|
||||
String group = convertPeriodToGroup(period);
|
||||
|
||||
if (utilizationMeasures != null) {
|
||||
Value production = utilizationMeasures.production;
|
||||
if (production != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(group, CHANNEL_ID_PRODUCTION), production);
|
||||
}
|
||||
|
||||
Value consumption = utilizationMeasures.consumption;
|
||||
if (consumption != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(group, CHANNEL_ID_CONSUMPTION), consumption);
|
||||
}
|
||||
|
||||
ValueAndPercent selfConsumptionForConsumption = utilizationMeasures.selfConsumptionForConsumption;
|
||||
if (selfConsumptionForConsumption != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(group, CHANNEL_ID_SELF_CONSUMPTION_FOR_CONSUMPTION),
|
||||
selfConsumptionForConsumption);
|
||||
putPercentType(result, channelProvider.getChannel(group, CHANNEL_ID_SELF_CONSUMPTION_COVERAGE),
|
||||
selfConsumptionForConsumption);
|
||||
}
|
||||
|
||||
Value batterySelfConsumption = utilizationMeasures.batterySelfConsumption;
|
||||
if (batterySelfConsumption != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(group, CHANNEL_ID_BATTERY_SELF_CONSUMPTION),
|
||||
batterySelfConsumption);
|
||||
}
|
||||
|
||||
Value imported = utilizationMeasures.imported;
|
||||
if (imported != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(group, CHANNEL_ID_IMPORT), imported);
|
||||
}
|
||||
|
||||
Value export = utilizationMeasures.export;
|
||||
if (export != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(group, CHANNEL_ID_EXPORT), export);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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.solaredge.internal.model;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.solaredge.internal.handler.ChannelProvider;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePublicApi.EnergyDetails;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePublicApi.MeterTelemetries;
|
||||
import org.openhab.binding.solaredge.internal.model.AggregateDataResponsePublicApi.MeterTelemetry;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* transforms the http response into the openhab datamodel (instances of State)
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AggregateDataResponseTransformerPublicApi extends AbstractDataResponseTransformer {
|
||||
private final Logger logger = LoggerFactory.getLogger(AggregateDataResponseTransformerPublicApi.class);
|
||||
|
||||
private static final String METER_TYPE_PRODUCTION = "Production";
|
||||
private static final String METER_TYPE_CONSUMPTION = "Consumption";
|
||||
private static final String METER_TYPE_SELFCONSUMPTION = "SelfConsumption";
|
||||
private static final String METER_TYPE_IMPORT = "Purchased";
|
||||
private static final String METER_TYPE_EXPORT = "FeedIn";
|
||||
|
||||
private final ChannelProvider channelProvider;
|
||||
|
||||
public AggregateDataResponseTransformerPublicApi(ChannelProvider channelProvider) {
|
||||
this.channelProvider = channelProvider;
|
||||
}
|
||||
|
||||
public Map<Channel, State> transform(AggregateDataResponsePublicApi response, AggregatePeriod period) {
|
||||
Map<Channel, State> result = new HashMap<>(20);
|
||||
EnergyDetails energyDetails = response.getEnergyDetails();
|
||||
|
||||
if (energyDetails != null) {
|
||||
AggregatePeriod timeUnit = energyDetails.timeUnit;
|
||||
String unit = energyDetails.unit;
|
||||
if (timeUnit != null && unit != null && energyDetails.meters != null) {
|
||||
for (MeterTelemetries meter : energyDetails.meters) {
|
||||
String type = meter.type;
|
||||
if (type != null) {
|
||||
if (type.equals(METER_TYPE_PRODUCTION)) {
|
||||
fillAggregateData(timeUnit, unit, meter, CHANNEL_ID_PRODUCTION, result);
|
||||
} else if (type.equals(METER_TYPE_CONSUMPTION)) {
|
||||
fillAggregateData(timeUnit, unit, meter, CHANNEL_ID_CONSUMPTION, result);
|
||||
} else if (type.equals(METER_TYPE_SELFCONSUMPTION)) {
|
||||
fillAggregateData(timeUnit, unit, meter, CHANNEL_ID_SELF_CONSUMPTION_FOR_CONSUMPTION,
|
||||
result);
|
||||
} else if (type.equals(METER_TYPE_IMPORT)) {
|
||||
fillAggregateData(timeUnit, unit, meter, CHANNEL_ID_IMPORT, result);
|
||||
} else if (type.equals(METER_TYPE_EXPORT)) {
|
||||
fillAggregateData(timeUnit, unit, meter, CHANNEL_ID_EXPORT, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
fillSelfConsumptionCoverage(timeUnit, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* copies production data to the result map
|
||||
*
|
||||
* @param meter meter raw data
|
||||
* @param valueMap target structure
|
||||
*/
|
||||
private final void fillAggregateData(AggregatePeriod period, String unit, MeterTelemetries meter, String channelId,
|
||||
Map<Channel, State> valueMap) {
|
||||
|
||||
String group = convertPeriodToGroup(period);
|
||||
List<MeterTelemetry> values = meter.values;
|
||||
|
||||
if (values != null) {
|
||||
switch (period) {
|
||||
case WEEK:
|
||||
if (values.size() == 1) {
|
||||
putEnergyType(valueMap, channelProvider.getChannel(group, channelId), unit, values.get(0));
|
||||
} else if (values.size() == 2) {
|
||||
putEnergyType(valueMap, channelProvider.getChannel(group, channelId), unit, values.get(0),
|
||||
values.get(1));
|
||||
} else {
|
||||
logger.warn("Response for weekly data has unexpected format, expected 2 entries got {}",
|
||||
values.size());
|
||||
}
|
||||
break;
|
||||
case DAY:
|
||||
case MONTH:
|
||||
case YEAR:
|
||||
putEnergyType(valueMap, channelProvider.getChannel(group, channelId), unit, values.get(0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates the self consumption coverage
|
||||
*
|
||||
* @param valueMap target structure
|
||||
*/
|
||||
private final void fillSelfConsumptionCoverage(AggregatePeriod period, Map<Channel, State> valueMap) {
|
||||
State selfConsumption = null;
|
||||
State consumption = null;
|
||||
|
||||
String group = convertPeriodToGroup(period);
|
||||
|
||||
selfConsumption = valueMap.get(channelProvider.getChannel(group, CHANNEL_ID_SELF_CONSUMPTION_FOR_CONSUMPTION));
|
||||
consumption = valueMap.get(channelProvider.getChannel(group, CHANNEL_ID_CONSUMPTION));
|
||||
putPercentType(valueMap, channelProvider.getChannel(group, CHANNEL_ID_SELF_CONSUMPTION_COVERAGE),
|
||||
selfConsumption, consumption);
|
||||
}
|
||||
}
|
||||
@@ -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.solaredge.internal.model;
|
||||
|
||||
/**
|
||||
* defines the level of data aggregation
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
public enum AggregatePeriod {
|
||||
DAY,
|
||||
WEEK,
|
||||
MONTH,
|
||||
YEAR
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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.solaredge.internal.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* this class is used to map the live data json response
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LiveDataResponse {
|
||||
public static final String GRID = "GRID";
|
||||
public static final String LOAD = "LOAD";
|
||||
public static final String PV = "PV";
|
||||
public static final String STORAGE = "STORAGE";
|
||||
|
||||
public static class Value {
|
||||
public @Nullable String status;
|
||||
public @Nullable Double currentPower;
|
||||
}
|
||||
|
||||
public static class BatteryValue {
|
||||
public @Nullable String status;
|
||||
public @Nullable Double currentPower;
|
||||
public @Nullable Double chargeLevel;
|
||||
public @Nullable String critical;
|
||||
}
|
||||
|
||||
public static class Connection {
|
||||
public @Nullable String from;
|
||||
public @Nullable String to;
|
||||
}
|
||||
|
||||
public static class SiteCurrentPowerFlow {
|
||||
public @Nullable String unit;
|
||||
|
||||
@SerializedName(GRID)
|
||||
public @Nullable Value grid;
|
||||
|
||||
@SerializedName(LOAD)
|
||||
public @Nullable Value load;
|
||||
|
||||
@SerializedName(PV)
|
||||
public @Nullable Value pv;
|
||||
|
||||
@SerializedName(STORAGE)
|
||||
public @Nullable BatteryValue storage;
|
||||
|
||||
public @Nullable List<Connection> connections;
|
||||
}
|
||||
|
||||
private @Nullable SiteCurrentPowerFlow siteCurrentPowerFlow;
|
||||
|
||||
public final @Nullable SiteCurrentPowerFlow getSiteCurrentPowerFlow() {
|
||||
return siteCurrentPowerFlow;
|
||||
}
|
||||
|
||||
public final void setSiteCurrentPowerFlow(SiteCurrentPowerFlow siteCurrentPowerFlow) {
|
||||
this.siteCurrentPowerFlow = siteCurrentPowerFlow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.solaredge.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* this class is used to map the live data json response
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LiveDataResponseMeterless {
|
||||
public static class Power {
|
||||
public @Nullable Double power;
|
||||
}
|
||||
|
||||
public static class Energy {
|
||||
public @Nullable Double energy;
|
||||
}
|
||||
|
||||
public static class Overview {
|
||||
public @Nullable Power currentPower;
|
||||
public @Nullable Energy lastDayData;
|
||||
public @Nullable Energy lastMonthData;
|
||||
public @Nullable Energy lastYearData;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Overview overview;
|
||||
|
||||
public final @Nullable Overview getOverview() {
|
||||
return overview;
|
||||
}
|
||||
|
||||
public final void setOverview(Overview overview) {
|
||||
this.overview = overview;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* 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.solaredge.internal.model;
|
||||
|
||||
import static org.openhab.binding.solaredge.internal.SolarEdgeBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.solaredge.internal.handler.ChannelProvider;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponse.BatteryValue;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponse.Connection;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponse.SiteCurrentPowerFlow;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponse.Value;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponseMeterless.Energy;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponseMeterless.Overview;
|
||||
import org.openhab.binding.solaredge.internal.model.LiveDataResponseMeterless.Power;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* transforms the http response into the openhab datamodel (instances of State)
|
||||
*
|
||||
* @author Alexander Friese - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LiveDataResponseTransformer extends AbstractDataResponseTransformer {
|
||||
private static final Double ZERO_POWER = 0.0;
|
||||
|
||||
private final ChannelProvider channelProvider;
|
||||
|
||||
public LiveDataResponseTransformer(ChannelProvider channelProvider) {
|
||||
this.channelProvider = channelProvider;
|
||||
}
|
||||
|
||||
public Map<Channel, State> transform(LiveDataResponseMeterless response) {
|
||||
Map<Channel, State> result = new HashMap<>(20);
|
||||
Overview overview = response.getOverview();
|
||||
|
||||
if (overview != null) {
|
||||
Power currentPower = overview.currentPower;
|
||||
if (currentPower != null) {
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_PRODUCTION),
|
||||
currentPower.power, UNIT_W);
|
||||
} else {
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_PRODUCTION), null,
|
||||
UNIT_W);
|
||||
}
|
||||
|
||||
Energy lastDayData = overview.lastDayData;
|
||||
if (lastDayData != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(CHANNEL_GROUP_AGGREGATE_DAY, CHANNEL_ID_PRODUCTION),
|
||||
lastDayData.energy, UNIT_WH);
|
||||
} else {
|
||||
putEnergyType(result, channelProvider.getChannel(CHANNEL_GROUP_AGGREGATE_DAY, CHANNEL_ID_PRODUCTION),
|
||||
null, UNIT_WH);
|
||||
}
|
||||
|
||||
Energy lastMonthData = overview.lastMonthData;
|
||||
if (lastMonthData != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(CHANNEL_GROUP_AGGREGATE_MONTH, CHANNEL_ID_PRODUCTION),
|
||||
lastMonthData.energy, UNIT_WH);
|
||||
} else {
|
||||
putEnergyType(result, channelProvider.getChannel(CHANNEL_GROUP_AGGREGATE_MONTH, CHANNEL_ID_PRODUCTION),
|
||||
null, UNIT_WH);
|
||||
}
|
||||
|
||||
Energy lastYearData = overview.lastYearData;
|
||||
if (lastYearData != null) {
|
||||
putEnergyType(result, channelProvider.getChannel(CHANNEL_GROUP_AGGREGATE_YEAR, CHANNEL_ID_PRODUCTION),
|
||||
lastYearData.energy, UNIT_WH);
|
||||
} else {
|
||||
putEnergyType(result, channelProvider.getChannel(CHANNEL_GROUP_AGGREGATE_YEAR, CHANNEL_ID_PRODUCTION),
|
||||
null, UNIT_WH);
|
||||
}
|
||||
|
||||
// week production is not available
|
||||
putEnergyType(result, channelProvider.getChannel(CHANNEL_GROUP_AGGREGATE_WEEK, CHANNEL_ID_PRODUCTION), null,
|
||||
UNIT_WH);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<Channel, State> transform(LiveDataResponse response) {
|
||||
Map<Channel, State> result = new HashMap<>(20);
|
||||
SiteCurrentPowerFlow siteCurrentPowerFlow = response.getSiteCurrentPowerFlow();
|
||||
|
||||
if (siteCurrentPowerFlow != null) {
|
||||
Value pv = siteCurrentPowerFlow.pv;
|
||||
Value load = siteCurrentPowerFlow.load;
|
||||
BatteryValue storage = siteCurrentPowerFlow.storage;
|
||||
Value grid = siteCurrentPowerFlow.grid;
|
||||
|
||||
if (pv != null) {
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_PRODUCTION),
|
||||
pv.currentPower, siteCurrentPowerFlow.unit);
|
||||
putStringType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_PV_STATUS), pv.status);
|
||||
}
|
||||
|
||||
if (load != null) {
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_CONSUMPTION),
|
||||
load.currentPower, siteCurrentPowerFlow.unit);
|
||||
putStringType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_LOAD_STATUS),
|
||||
load.status);
|
||||
}
|
||||
|
||||
if (storage != null) {
|
||||
putStringType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_STATUS),
|
||||
storage.status);
|
||||
putStringType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_CRITICAL),
|
||||
storage.critical);
|
||||
putPercentType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_LEVEL),
|
||||
storage.chargeLevel);
|
||||
}
|
||||
|
||||
if (grid != null) {
|
||||
putStringType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_GRID_STATUS),
|
||||
grid.status);
|
||||
}
|
||||
|
||||
// init fields with zero
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_IMPORT), ZERO_POWER,
|
||||
siteCurrentPowerFlow.unit);
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_EXPORT), ZERO_POWER,
|
||||
siteCurrentPowerFlow.unit);
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_CHARGE), ZERO_POWER,
|
||||
siteCurrentPowerFlow.unit);
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_DISCHARGE),
|
||||
ZERO_POWER, siteCurrentPowerFlow.unit);
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_CHARGE_DISCHARGE),
|
||||
ZERO_POWER, siteCurrentPowerFlow.unit);
|
||||
|
||||
// determine power flow from connection list
|
||||
if (siteCurrentPowerFlow.connections != null) {
|
||||
for (Connection con : siteCurrentPowerFlow.connections) {
|
||||
if (grid != null) {
|
||||
if (con.from != null && con.from.equalsIgnoreCase(LiveDataResponse.GRID)) {
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_IMPORT),
|
||||
grid.currentPower, siteCurrentPowerFlow.unit);
|
||||
} else if (con.to != null && con.to.equalsIgnoreCase(LiveDataResponse.GRID)) {
|
||||
putPowerType(result, channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_EXPORT),
|
||||
grid.currentPower, siteCurrentPowerFlow.unit);
|
||||
}
|
||||
}
|
||||
|
||||
if (storage != null) {
|
||||
Double currentPower = storage.currentPower != null ? storage.currentPower : 0;
|
||||
if (con.from != null && con.from.equalsIgnoreCase(LiveDataResponse.STORAGE)) {
|
||||
putPowerType(result,
|
||||
channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_DISCHARGE),
|
||||
currentPower, siteCurrentPowerFlow.unit);
|
||||
putPowerType(result,
|
||||
channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_CHARGE_DISCHARGE),
|
||||
-1 * currentPower, siteCurrentPowerFlow.unit);
|
||||
} else if (con.to != null && con.to.equalsIgnoreCase(LiveDataResponse.STORAGE)) {
|
||||
putPowerType(result,
|
||||
channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_CHARGE),
|
||||
currentPower, siteCurrentPowerFlow.unit);
|
||||
putPowerType(result,
|
||||
channelProvider.getChannel(CHANNEL_GROUP_LIVE, CHANNEL_ID_BATTERY_CHARGE_DISCHARGE),
|
||||
currentPower, siteCurrentPowerFlow.unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="solaredge" 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>SolarEdge Binding</name>
|
||||
<description>This is the binding for SolarEdge.</description>
|
||||
<author>Alexander Friese</author>
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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:solaredge:web">
|
||||
<parameter-group name="authentication">
|
||||
<label>Authentication</label>
|
||||
<description>Authentication settings.</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="connection">
|
||||
<label>Connection</label>
|
||||
<description>Connection settings.</description>
|
||||
</parameter-group>
|
||||
<parameter-group name="general">
|
||||
<label>General</label>
|
||||
<description>General settings.</description>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="tokenOrApiKey" type="text" required="true" groupName="authentication">
|
||||
<label>Token or API Key</label>
|
||||
<description>API Key to access the official solaredge API. If using Private API this must be fills with Spring
|
||||
Security Token (Check Browser Cookies when logged into website)</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="solarId" type="text" required="true" groupName="general">
|
||||
<label>Solar ID</label>
|
||||
<description>The ID to identify the solarplant at SolarEdge.</description>
|
||||
</parameter>
|
||||
<parameter name="meterInstalled" type="boolean" required="false" groupName="general">
|
||||
<label>Meter Installed</label>
|
||||
<description>If your setup contains a modbus meter, please activate this, to get more detailed data.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="usePrivateApi" type="boolean" required="false" groupName="general">
|
||||
<label>Private API</label>
|
||||
<description>Private API allows to avoid the limit of 300 API calls per day but is less documented and therefore less
|
||||
stable. If no meter is available you cannot use the private API, it does not provide live data for this kind of
|
||||
setup.</description>
|
||||
<default>false</default>
|
||||
</parameter>
|
||||
<parameter name="liveDataPollingInterval" type="integer" required="false" min="1" max="60" unit="m"
|
||||
groupName="connection">
|
||||
<label>Polling Interval</label>
|
||||
<description>Interval in which live data is polled from SolarEdge (in minutes). If not using private API this should
|
||||
not be less than 10 minutes.</description>
|
||||
<default>10</default>
|
||||
</parameter>
|
||||
<parameter name="aggregateDataPollingInterval" type="integer" required="false" min="5" max="1440" unit="m"
|
||||
groupName="connection">
|
||||
<label>Polling Interval</label>
|
||||
<description>Interval in which aggregate data is polled from SolarEdge (in minutes). If not using private API this
|
||||
must not be less than 60 minutes.</description>
|
||||
<default>60</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
@@ -0,0 +1,28 @@
|
||||
# binding
|
||||
binding.solaredge.name = SolarEdge Binding
|
||||
binding.solaredge.description = Abfrage von Kennzahlen der Photovoltaikanlage über die Web-API von SolarEdge.
|
||||
|
||||
# thing types
|
||||
thing-type.solaredge.web.label = SolarEdge Wechselrichter
|
||||
thing-type.solaredge.web.description = WebZugang zu SolarEdge
|
||||
|
||||
# groups
|
||||
thing-type.config.solaredge.web.group.authentication.label = Authentifizierung
|
||||
thing-type.config.solaredge.web.group.authentication.description = Einstellungen für die Authentifizierung.
|
||||
thing-type.config.solaredge.web.group.connection.label = Verbindung
|
||||
thing-type.config.solaredge.web.group.connection.description = Einstellungen für die Verbindung.
|
||||
thing-type.config.solaredge.web.group.general.label = Allgemeines
|
||||
thing-type.config.solaredge.web.group.general.description = Allgemeine Einstellungen.
|
||||
|
||||
thing-type.config.solaredge.web.tokenOrApiKey.label = Token oder API-Key
|
||||
thing-type.config.solaredge.web.tokenOrApiKey.description = Spring Security Token (Browser Cookie bei Verwendung der Solaredge Webseite) bei Verwendung der Private API, API-Key bei Verwendung der Public API.
|
||||
thing-type.config.solaredge.web.solarId.label = SolarEdge ID
|
||||
thing-type.config.solaredge.web.solarId.description = ID der PV-Anlage bei SolarEdge.
|
||||
thing-type.config.solaredge.web.usePrivateApi.label = Private API
|
||||
thing-type.config.solaredge.web.usePrivateApi.description = Die Private API umgeht die Einschränkung auf 300 API Aufrufen pro Tag, sie ist jedoch nicht offiziell dokumentiert und es gibt keine Funktionsgarantie. Ohne ein Meter funktioniert diese API nicht, da in diesem Fall keine Live-Daten zur Verfügung gestellt werden.
|
||||
thing-type.config.solaredge.web.meterInstalled.label = Modbus Meter vorhanden
|
||||
thing-type.config.solaredge.web.meterInstalled.description = Sofern ein Modbus Meter vorhanden ist, sollte dies unbedingt aktiviert werden, um detailliertere Daten zu erhalten.
|
||||
thing-type.config.solaredge.web.liveDataPollingInterval.label = Abfrageintervall Livedaten
|
||||
thing-type.config.solaredge.web.liveDataPollingInterval.description = Intervall in welchem Abfragen für Live-Daten an SolarEdge geschickt werden.
|
||||
thing-type.config.solaredge.web.aggregateDataPollingInterval.label = Abfrageintervall Aggregatedaten
|
||||
thing-type.config.solaredge.web.aggregateDataPollingInterval.description = Intervall in welchem Abfragen für aggregierte Daten an SolarEdge geschickt werden.
|
||||
@@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="solaredge"
|
||||
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">
|
||||
<channel-group-type id="genericweb-live">
|
||||
<label>Live Data</label>
|
||||
<channels>
|
||||
<channel id="production" typeId="type-power">
|
||||
<label>Production</label>
|
||||
<description>Current Production</description>
|
||||
</channel>
|
||||
<channel id="pv_status" typeId="type-status">
|
||||
<label>PV Status</label>
|
||||
<description>Current PV Status</description>
|
||||
</channel>
|
||||
<channel id="consumption" typeId="type-power">
|
||||
<label>Consumption</label>
|
||||
<description>Current Consumption</description>
|
||||
</channel>
|
||||
<channel id="load_status" typeId="type-status">
|
||||
<label>Load Status</label>
|
||||
<description>Current Load Status</description>
|
||||
</channel>
|
||||
<channel id="battery_charge" typeId="type-power">
|
||||
<label>Battery Charge</label>
|
||||
<description>Current Charge Rate</description>
|
||||
</channel>
|
||||
<channel id="battery_discharge" typeId="type-power">
|
||||
<label>Battery Discharge</label>
|
||||
<description>Current Discharge Rate</description>
|
||||
</channel>
|
||||
<channel id="battery_charge_discharge" typeId="type-power">
|
||||
<label>Battery Charge/Discharge</label>
|
||||
<description>Current Charge/Discharge Rate</description>
|
||||
</channel>
|
||||
<channel id="battery_level" typeId="type-percent">
|
||||
<label>Battery Level</label>
|
||||
<description>Current Charge Level</description>
|
||||
</channel>
|
||||
<channel id="battery_status" typeId="type-status">
|
||||
<label>Battery Status</label>
|
||||
<description>Current Battery Status</description>
|
||||
</channel>
|
||||
<channel id="battery_critical" typeId="type-status">
|
||||
<label>Battery Critical</label>
|
||||
<description>Battery Critical</description>
|
||||
</channel>
|
||||
<channel id="import" typeId="type-power">
|
||||
<label>Import</label>
|
||||
<description>Current Import from Grid</description>
|
||||
</channel>
|
||||
<channel id="export" typeId="type-power">
|
||||
<label>Export</label>
|
||||
<description>Current Export to Grid</description>
|
||||
</channel>
|
||||
<channel id="grid_status" typeId="type-status">
|
||||
<label>Grid Status</label>
|
||||
<description>Current Grid Status</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="genericweb-aggregate-day">
|
||||
<label>Aggregate Day Data</label>
|
||||
<description>Aggregate data (by day)</description>
|
||||
<channels>
|
||||
<channel id="production" typeId="type-energy">
|
||||
<label>Production</label>
|
||||
<description>Aggregate Day Production</description>
|
||||
</channel>
|
||||
<channel id="consumption" typeId="type-energy">
|
||||
<label>Consumption</label>
|
||||
<description>Aggregate Day Consumption</description>
|
||||
</channel>
|
||||
<channel id="selfConsumptionForConsumption" typeId="type-energy">
|
||||
<label>Self Consumption</label>
|
||||
<description>Aggregate Day Self Consumption</description>
|
||||
</channel>
|
||||
<channel id="selfConsumptionCoverage" typeId="type-percent">
|
||||
<label>Self Consumption Coverage</label>
|
||||
<description>Aggregate Day Self Consumption Coverage</description>
|
||||
</channel>
|
||||
<channel id="batterySelfConsumption" typeId="type-energy">
|
||||
<label>Battery Self Consumption</label>
|
||||
<description>Aggregate Day Battery Self Consumption</description>
|
||||
</channel>
|
||||
<channel id="import" typeId="type-energy">
|
||||
<label>Import</label>
|
||||
<description>Aggregate Day Import</description>
|
||||
</channel>
|
||||
<channel id="export" typeId="type-energy">
|
||||
<label>Export</label>
|
||||
<description>Aggregate Day Export</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="genericweb-aggregate-week">
|
||||
<label>Aggregate Week Data</label>
|
||||
<description>Aggregate data (by week)</description>
|
||||
<channels>
|
||||
<channel id="production" typeId="type-energy">
|
||||
<label>Production</label>
|
||||
<description>Aggregate Week Production</description>
|
||||
</channel>
|
||||
<channel id="consumption" typeId="type-energy">
|
||||
<label>Consumption</label>
|
||||
<description>Aggregate Week Consumption</description>
|
||||
</channel>
|
||||
<channel id="selfConsumptionForConsumption" typeId="type-energy">
|
||||
<label>Self Consumption</label>
|
||||
<description>Aggregate Week Self Consumption</description>
|
||||
</channel>
|
||||
<channel id="selfConsumptionCoverage" typeId="type-percent">
|
||||
<label>Self Consumption Coverage</label>
|
||||
<description>Aggregate Week Self Consumption Coverage</description>
|
||||
</channel>
|
||||
<channel id="batterySelfConsumption" typeId="type-energy">
|
||||
<label>Battery Self Consumption</label>
|
||||
<description>Aggregate Week Battery Self Consumption</description>
|
||||
</channel>
|
||||
<channel id="import" typeId="type-energy">
|
||||
<label>Import</label>
|
||||
<description>Aggregate Week Import</description>
|
||||
</channel>
|
||||
<channel id="export" typeId="type-energy">
|
||||
<label>Export</label>
|
||||
<description>Aggregate Week Export</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="genericweb-aggregate-month">
|
||||
<label>Aggregate Month Data</label>
|
||||
<description>Aggregate data (by month)</description>
|
||||
<channels>
|
||||
<channel id="production" typeId="type-energy">
|
||||
<label>Production</label>
|
||||
<description>Aggregate Month Production</description>
|
||||
</channel>
|
||||
<channel id="consumption" typeId="type-energy">
|
||||
<label>Consumption</label>
|
||||
<description>Aggregate Month Consumption</description>
|
||||
</channel>
|
||||
<channel id="selfConsumptionForConsumption" typeId="type-energy">
|
||||
<label>Self Consumption</label>
|
||||
<description>Aggregate Month Self Consumption</description>
|
||||
</channel>
|
||||
<channel id="selfConsumptionCoverage" typeId="type-percent">
|
||||
<label>Self Consumption Coverage</label>
|
||||
<description>Aggregate Month Self Consumption Coverage</description>
|
||||
</channel>
|
||||
<channel id="batterySelfConsumption" typeId="type-energy">
|
||||
<label>Battery Self Consumption</label>
|
||||
<description>Aggregate Month Battery Self Consumption</description>
|
||||
</channel>
|
||||
<channel id="import" typeId="type-energy">
|
||||
<label>Import</label>
|
||||
<description>Aggregate Month Import</description>
|
||||
</channel>
|
||||
<channel id="export" typeId="type-energy">
|
||||
<label>Export</label>
|
||||
<description>Aggregate Month Export</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
<channel-group-type id="genericweb-aggregate-year">
|
||||
<label>Aggregate Year Data</label>
|
||||
<description>Aggregate data (by year)</description>
|
||||
<channels>
|
||||
<channel id="production" typeId="type-energy">
|
||||
<label>Production</label>
|
||||
<description>Aggregate Year Production</description>
|
||||
</channel>
|
||||
<channel id="consumption" typeId="type-energy">
|
||||
<label>Consumption</label>
|
||||
<description>Aggregate Year Consumption</description>
|
||||
</channel>
|
||||
<channel id="selfConsumptionForConsumption" typeId="type-energy">
|
||||
<label>Self Consumption</label>
|
||||
<description>Aggregate Year Self Consumption</description>
|
||||
</channel>
|
||||
<channel id="selfConsumptionCoverage" typeId="type-percent">
|
||||
<label>Self Consumption Coverage</label>
|
||||
<description>Aggregate Year Self Consumption Coverage</description>
|
||||
</channel>
|
||||
<channel id="batterySelfConsumption" typeId="type-energy">
|
||||
<label>Battery Self Consumption</label>
|
||||
<description>Aggregate Year Battery Self Consumption</description>
|
||||
</channel>
|
||||
<channel id="import" typeId="type-energy">
|
||||
<label>Import</label>
|
||||
<description>Aggregate Year Import</description>
|
||||
</channel>
|
||||
<channel id="export" typeId="type-energy">
|
||||
<label>Export</label>
|
||||
<description>Aggregate Year Export</description>
|
||||
</channel>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="solaredge"
|
||||
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">
|
||||
<channel-type id="type-power">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Power</label>
|
||||
<state pattern="%.2f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-energy">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Energy</label>
|
||||
<state pattern="%.1f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-status">
|
||||
<item-type>String</item-type>
|
||||
<label>Status Text</label>
|
||||
<state pattern="%s" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="type-percent">
|
||||
<item-type>Number:Dimensionless</item-type>
|
||||
<label>Percent</label>
|
||||
<state pattern="%.0f %unit%" readOnly="true">
|
||||
</state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="solaredge"
|
||||
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">
|
||||
<thing-type id="generic">
|
||||
<label>SolarEdge</label>
|
||||
<description>data retrieved from SolarEdge web interface</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group typeId="genericweb-live" id="live"/>
|
||||
<channel-group typeId="genericweb-aggregate-day" id="aggregate_day"/>
|
||||
<channel-group typeId="genericweb-aggregate-week" id="aggregate_week"/>
|
||||
<channel-group typeId="genericweb-aggregate-month" id="aggregate_month"/>
|
||||
<channel-group typeId="genericweb-aggregate-year" id="aggregate_year"/>
|
||||
</channel-groups>
|
||||
|
||||
<config-description-ref uri="thing-type:solaredge:web"/>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user