Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 87 additions & 70 deletions bundles/org.openhab.binding.easee/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ public class EaseeBindingConstants {
public static final String DEVICE_SITE = "site";
public static final String DEVICE_MASTER_CHARGER = "mastercharger";
public static final String DEVICE_CHARGER = "charger";
public static final String DEVICE_USER = "user";

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_SITE = new ThingTypeUID(BINDING_ID, DEVICE_SITE);
public static final ThingTypeUID THING_TYPE_MASTER_CHARGER = new ThingTypeUID(BINDING_ID, DEVICE_MASTER_CHARGER);
public static final ThingTypeUID THING_TYPE_CHARGER = new ThingTypeUID(BINDING_ID, DEVICE_CHARGER);
public static final ThingTypeUID THING_TYPE_USER = new ThingTypeUID(BINDING_ID, DEVICE_USER);

// List of all channel groups
public static final String CHANNEL_GROUP_NONE = "";
Expand All @@ -49,6 +51,7 @@ public class EaseeBindingConstants {
public static final String CHANNEL_GROUP_CHARGER_LATEST_SESSION = "latestSession";
public static final String CHANNEL_GROUP_CIRCUIT_DYNAMIC_CURRENT = "dynamicCurrent";
public static final String CHANNEL_GROUP_CIRCUIT_SETTINGS = "settings";
public static final String CHANNEL_GROUP_USER_CONSUMPTION = "consumption";

// Channel types
public static final String CHANNEL_TYPE_SWITCH = "Switch";
Expand Down Expand Up @@ -83,6 +86,7 @@ public class EaseeBindingConstants {
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE1 = "offlineMaxCircuitCurrentP1";
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE2 = "offlineMaxCircuitCurrentP2";
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE3 = "offlineMaxCircuitCurrentP3";
public static final String CHANNEL_USER_TOTAL_CONSUMPTION = "totalConsumption";

// JSON Keys
public static final String JSON_KEY_GENERIC_ID = "id";
Expand All @@ -104,6 +108,11 @@ public class EaseeBindingConstants {
public static final String JSON_KEY_CIRCUIT_STATES = "circuitStates";
public static final String JSON_KEY_CHARGER_STATES = "chargerStates";
public static final String JSON_KEY_CHARGER_STATE = "chargerState";
public static final String JSON_KEY_SITE_USERS = "siteUsers";
public static final String JSON_KEY_USER_ID = "userId";
public static final String JSON_KEY_USER_EMAIL = "email";
public static final String JSON_KEY_USER_PHONE_NUMBER = "phoneNumber";
public static final String JSON_KEY_TOTAL_ENERGY_USAGE = "totalEnergyUsage";

// Write Commands
public static final String COMMAND_CHANGE_CONFIGURATION = "ChangeConfiguration";
Expand Down Expand Up @@ -132,6 +141,7 @@ public class EaseeBindingConstants {
public static final String LOGIN_URL = API_BASE_URL + "/accounts/login";
public static final String REFRESH_TOKEN_URL = API_BASE_URL + "/accounts/refresh_token";
public static final String GET_SITE_URL = API_BASE_URL + "/sites/{siteId}";
public static final String GET_SITE_USERS_URL = API_BASE_URL + "/sites/{siteId}/users";
public static final String CHARGER_URL = API_BASE_URL + "/chargers/{id}";
public static final String SITE_STATE_URL = API_BASE_URL + "/sites/{siteId}/state";
public static final String GET_CONFIGURATION_URL = API_BASE_URL + "/chargers/{id}/config";
Expand All @@ -141,6 +151,7 @@ public class EaseeBindingConstants {
public static final String DYNAMIC_CIRCUIT_CURRENT_URL = API_BASE_URL
+ "/sites/{siteId}/circuits/{circuitId}/dynamicCurrent";
public static final String CIRCUIT_SETTINGS_URL = API_BASE_URL + "/sites/{siteId}/circuits/{circuitId}/settings";
public static final String USER_TOTAL_CONSUMPTION_URL = API_BASE_URL + "/sites/{siteId}/users/{userId}/yearly";

// Status Keys
public static final String STATUS_TOKEN_VALIDATED = "@text/status.token.validated";
Expand Down Expand Up @@ -173,5 +184,5 @@ public class EaseeBindingConstants {
public static final String PARAMETER_NAME_VALIDATION_REGEXP = "validationExpression";

public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SITE,
THING_TYPE_MASTER_CHARGER, THING_TYPE_CHARGER);
THING_TYPE_MASTER_CHARGER, THING_TYPE_CHARGER, THING_TYPE_USER);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.openhab.binding.easee.internal.handler.EaseeChargerHandler;
import org.openhab.binding.easee.internal.handler.EaseeMasterChargerHandler;
import org.openhab.binding.easee.internal.handler.EaseeSiteHandler;
import org.openhab.binding.easee.internal.handler.EaseeUserHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
Expand Down Expand Up @@ -70,6 +71,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return new EaseeMasterChargerHandler(thing);
} else if (THING_TYPE_CHARGER.equals(thingTypeUID)) {
return new EaseeChargerHandler(thing);
} else if (THING_TYPE_USER.equals(thingTypeUID)) {
return new EaseeUserHandler(thing);
} else {
logger.warn("Unsupported Thing-Type: {}", thingTypeUID.getAsString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.ToNumberPolicy;

Expand Down Expand Up @@ -127,10 +128,8 @@ public AbstractCommand(EaseeThingHandler handler, RetryOnFailure retryOnFailure,
@Override
public final void onSuccess(Response response) {
super.onSuccess(response);
if (response != null) {
communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
logger.debug("HTTP response {}", response.getStatus());
}
communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
logger.debug("HTTP response {}", response.getStatus());
}

/**
Expand Down Expand Up @@ -165,7 +164,8 @@ public final void onFailure(@Nullable Response response, @Nullable Throwable fai
@Override
public void onContent(Response response, ByteBuffer content) {
super.onContent(response, content);
logger.debug("received content, length: {}", getContentAsString().length());
String contentAsString = getContentAsString();
logger.debug("received content, length: {}", contentAsString != null ? contentAsString.length() : 0);
}

/**
Expand All @@ -192,7 +192,7 @@ public void onComplete(@Nullable Result result) {
* @param json
*/
protected void onCompleteCodeOk(@Nullable String json) {
JsonObject jsonObject = transform(json);
JsonObject jsonObject = transform(json, JsonObject.class);
if (jsonObject != null) {
logger.debug("success");
handler.updateChannelStatus(transformer.transform(jsonObject, getChannelGroup()));
Expand All @@ -206,7 +206,7 @@ protected void onCompleteCodeOk(@Nullable String json) {
* @param json
*/
protected void onCompleteCodeDefault(@Nullable String json) {
JsonObject jsonObject = transform(json);
JsonObject jsonObject = transform(json, JsonObject.class);
if (jsonObject == null) {
jsonObject = new JsonObject();
}
Expand All @@ -226,12 +226,13 @@ protected void onCompleteCodeDefault(@Nullable String json) {
* error safe json transformer.
*
* @param json
* @param clazz target type
* @return
*/
protected @Nullable JsonObject transform(@Nullable String json) {
protected <T extends JsonElement> @Nullable T transform(@Nullable String json, Class<T> clazz) {
if (json != null) {
try {
return gson.fromJson(json, JsonObject.class);
return gson.fromJson(json, clazz);
} catch (Exception ex) {
logger.debug("JSON could not be parsed: {}\nError: {}", json, ex.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2010-2026 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.easee.internal.command.account;

import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.easee.internal.command.AbstractCommand;
import org.openhab.binding.easee.internal.command.JsonResultProcessor;
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

/**
* implements the get yearly consumption by user api call and computes a total sum.
*
* @author Alexander Friese - initial contribution
*/
@NonNullByDefault
public class GetUserTotalConsumption extends AbstractCommand {

private final String url;

public GetUserTotalConsumption(EaseeThingHandler handler, String userId, JsonResultProcessor resultProcessor) {
// retry does not make much sense as it is a polling command, command should always succeed therefore update
// handler on failure.
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES, resultProcessor);
String siteId = handler.getBridgeConfiguration().getSiteId();
this.url = USER_TOTAL_CONSUMPTION_URL.replaceAll("\\{siteId\\}", siteId).replaceAll("\\{userId\\}", userId);
}

@Override
protected Request prepareRequest(Request requestToPrepare) {
requestToPrepare.method(HttpMethod.GET);
return requestToPrepare;
}

@Override
protected String getURL() {
return url;
}

@Override
protected String getChannelGroup() {
return CHANNEL_GROUP_USER_CONSUMPTION;
}

@Override
protected void onCompleteCodeOk(@Nullable String json) {
JsonArray yearlyData = transform(json, JsonArray.class);
if (yearlyData != null) {
double totalConsumption = 0;
for (JsonElement element : yearlyData) {
JsonObject yearEntry = element.getAsJsonObject();
if (yearEntry.has(JSON_KEY_TOTAL_ENERGY_USAGE)) {
totalConsumption += yearEntry.get(JSON_KEY_TOTAL_ENERGY_USAGE).getAsDouble();
}
}

JsonObject jsonObject = new JsonObject();
jsonObject.addProperty(CHANNEL_USER_TOTAL_CONSUMPTION, totalConsumption);
handler.updateChannelStatus(transformer.transform(jsonObject, getChannelGroup()));
processResult(jsonObject);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2010-2026 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.easee.internal.command.site;

import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpMethod;
import org.openhab.binding.easee.internal.command.AbstractCommand;
import org.openhab.binding.easee.internal.command.JsonResultProcessor;
import org.openhab.binding.easee.internal.handler.EaseeBridgeHandler;

/**
* implements the get site users api call.
*
* @author Alexander Friese - initial contribution
*/
@NonNullByDefault
public class GetSiteUsers extends AbstractCommand {

public GetSiteUsers(EaseeBridgeHandler handler, JsonResultProcessor resultProcessor) {
// retry does not make much sense as it is a polling command, command should always succeed therefore update
// handler on failure.
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES, resultProcessor);
}

@Override
protected Request prepareRequest(Request requestToPrepare) {
requestToPrepare.method(HttpMethod.GET);
return requestToPrepare;
}

@Override
protected String getURL() {
String url = GET_SITE_USERS_URL;
return url.replaceAll("\\{siteId\\}", handler.getBridgeConfiguration().getSiteId());
}

@Override
protected String getChannelGroup() {
return CHANNEL_GROUP_SITE_INFO;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ protected String getChannelGroup() {
*/
@Override
protected void onCompleteCodeOk(@Nullable String json) {
JsonObject jsonObject = transform(json);
JsonObject jsonObject = transform(json, JsonObject.class);

if (jsonObject != null) {
logger.debug("success");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.openhab.binding.easee.internal.EaseeBindingConstants;
import org.openhab.binding.easee.internal.Utils;
import org.openhab.binding.easee.internal.command.site.GetSite;
import org.openhab.binding.easee.internal.command.site.GetSiteUsers;
import org.openhab.binding.easee.internal.connector.CommunicationStatus;
import org.openhab.binding.easee.internal.handler.EaseeSiteHandler;
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
Expand Down Expand Up @@ -53,6 +54,7 @@ public EaseeSiteDiscoveryService() throws IllegalArgumentException {
@Override
protected void startScan() {
thingHandler.enqueueCommand(new GetSite(thingHandler, this::processSiteDiscoveryResult));
thingHandler.enqueueCommand(new GetSiteUsers(thingHandler, this::processSiteUsersDiscoveryResult));
}

@Override
Expand Down Expand Up @@ -153,4 +155,54 @@ private DiscoveryResultBuilder initDiscoveryResultBuilder(String deviceType, Str
return DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID).withLabel(label)
.withProperty(THING_CONFIG_ID, deviceId).withRepresentationProperty(THING_CONFIG_ID);
}

/**
* callback that handles json result data for site users discovery.
*
* @param status
* @param siteUsers
*/
private void processSiteUsersDiscoveryResult(CommunicationStatus status, JsonObject siteUsers) {
logger.debug("processSiteUsersDiscoveryResult {}", siteUsers);

JsonArray users = siteUsers.getAsJsonArray(JSON_KEY_SITE_USERS);
if (users == null) {
logger.info("Site users discovery failed, no users found.");
} else {
users.forEach(this::handleUserDiscovery);
}
}

/**
* handles each user discovery result.
*
* @param user
*/
private void handleUserDiscovery(JsonElement json) {
logger.debug("handleUserDiscovery {}", json);

JsonObject user = json.getAsJsonObject();
String userId = Utils.getAsString(user, JSON_KEY_USER_ID);
String userName = Utils.getAsString(user, JSON_KEY_GENERIC_NAME);
String userEmail = Utils.getAsString(user, JSON_KEY_USER_EMAIL);
String userPhoneNumber = Utils.getAsString(user, JSON_KEY_USER_PHONE_NUMBER);

if (userId != null) {
String userLabel = userName != null ? userName : (userEmail != null ? userEmail : userId);

DiscoveryResultBuilder builder = initDiscoveryResultBuilder(DEVICE_USER, userId, userLabel);

if (userEmail != null) {
builder.withProperty(JSON_KEY_USER_EMAIL, userEmail);
}
if (userName != null) {
builder.withProperty(JSON_KEY_GENERIC_NAME, userName);
}
if (userPhoneNumber != null) {
builder.withProperty(JSON_KEY_USER_PHONE_NUMBER, userPhoneNumber);
}

thingDiscovered(builder.build());
}
}
}
Loading
Loading