Skip to content

Commit 0e270ed

Browse files
authored
[easee] add ability to retrieve total consumption per user (#20176)
* added user discovery Signed-off-by: Alexander Friese <af944580@googlemail.com>
1 parent 7abaa16 commit 0e270ed

14 files changed

Lines changed: 605 additions & 202 deletions

File tree

bundles/org.openhab.binding.easee/README.md

Lines changed: 87 additions & 70 deletions
Large diffs are not rendered by default.

bundles/org.openhab.binding.easee/src/main/java/org/openhab/binding/easee/internal/EaseeBindingConstants.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ public class EaseeBindingConstants {
3333
public static final String DEVICE_SITE = "site";
3434
public static final String DEVICE_MASTER_CHARGER = "mastercharger";
3535
public static final String DEVICE_CHARGER = "charger";
36+
public static final String DEVICE_USER = "user";
3637

3738
// List of all Thing Type UIDs
3839
public static final ThingTypeUID THING_TYPE_SITE = new ThingTypeUID(BINDING_ID, DEVICE_SITE);
3940
public static final ThingTypeUID THING_TYPE_MASTER_CHARGER = new ThingTypeUID(BINDING_ID, DEVICE_MASTER_CHARGER);
4041
public static final ThingTypeUID THING_TYPE_CHARGER = new ThingTypeUID(BINDING_ID, DEVICE_CHARGER);
42+
public static final ThingTypeUID THING_TYPE_USER = new ThingTypeUID(BINDING_ID, DEVICE_USER);
4143

4244
// List of all channel groups
4345
public static final String CHANNEL_GROUP_NONE = "";
@@ -49,6 +51,7 @@ public class EaseeBindingConstants {
4951
public static final String CHANNEL_GROUP_CHARGER_LATEST_SESSION = "latestSession";
5052
public static final String CHANNEL_GROUP_CIRCUIT_DYNAMIC_CURRENT = "dynamicCurrent";
5153
public static final String CHANNEL_GROUP_CIRCUIT_SETTINGS = "settings";
54+
public static final String CHANNEL_GROUP_USER_CONSUMPTION = "consumption";
5255

5356
// Channel types
5457
public static final String CHANNEL_TYPE_SWITCH = "Switch";
@@ -83,6 +86,7 @@ public class EaseeBindingConstants {
8386
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE1 = "offlineMaxCircuitCurrentP1";
8487
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE2 = "offlineMaxCircuitCurrentP2";
8588
public static final String CHANNEL_CIRCUIT_OFFLINE_MAX_CURRENT_PHASE3 = "offlineMaxCircuitCurrentP3";
89+
public static final String CHANNEL_USER_TOTAL_CONSUMPTION = "totalConsumption";
8690

8791
// JSON Keys
8892
public static final String JSON_KEY_GENERIC_ID = "id";
@@ -104,6 +108,11 @@ public class EaseeBindingConstants {
104108
public static final String JSON_KEY_CIRCUIT_STATES = "circuitStates";
105109
public static final String JSON_KEY_CHARGER_STATES = "chargerStates";
106110
public static final String JSON_KEY_CHARGER_STATE = "chargerState";
111+
public static final String JSON_KEY_SITE_USERS = "siteUsers";
112+
public static final String JSON_KEY_USER_ID = "userId";
113+
public static final String JSON_KEY_USER_EMAIL = "email";
114+
public static final String JSON_KEY_USER_PHONE_NUMBER = "phoneNumber";
115+
public static final String JSON_KEY_TOTAL_ENERGY_USAGE = "totalEnergyUsage";
107116

108117
// Write Commands
109118
public static final String COMMAND_CHANGE_CONFIGURATION = "ChangeConfiguration";
@@ -132,6 +141,7 @@ public class EaseeBindingConstants {
132141
public static final String LOGIN_URL = API_BASE_URL + "/accounts/login";
133142
public static final String REFRESH_TOKEN_URL = API_BASE_URL + "/accounts/refresh_token";
134143
public static final String GET_SITE_URL = API_BASE_URL + "/sites/{siteId}";
144+
public static final String GET_SITE_USERS_URL = API_BASE_URL + "/sites/{siteId}/users";
135145
public static final String CHARGER_URL = API_BASE_URL + "/chargers/{id}";
136146
public static final String SITE_STATE_URL = API_BASE_URL + "/sites/{siteId}/state";
137147
public static final String GET_CONFIGURATION_URL = API_BASE_URL + "/chargers/{id}/config";
@@ -141,6 +151,7 @@ public class EaseeBindingConstants {
141151
public static final String DYNAMIC_CIRCUIT_CURRENT_URL = API_BASE_URL
142152
+ "/sites/{siteId}/circuits/{circuitId}/dynamicCurrent";
143153
public static final String CIRCUIT_SETTINGS_URL = API_BASE_URL + "/sites/{siteId}/circuits/{circuitId}/settings";
154+
public static final String USER_TOTAL_CONSUMPTION_URL = API_BASE_URL + "/sites/{siteId}/users/{userId}/yearly";
144155

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

175186
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SITE,
176-
THING_TYPE_MASTER_CHARGER, THING_TYPE_CHARGER);
187+
THING_TYPE_MASTER_CHARGER, THING_TYPE_CHARGER, THING_TYPE_USER);
177188
}

bundles/org.openhab.binding.easee/src/main/java/org/openhab/binding/easee/internal/EaseeHandlerFactory.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.openhab.binding.easee.internal.handler.EaseeChargerHandler;
2121
import org.openhab.binding.easee.internal.handler.EaseeMasterChargerHandler;
2222
import org.openhab.binding.easee.internal.handler.EaseeSiteHandler;
23+
import org.openhab.binding.easee.internal.handler.EaseeUserHandler;
2324
import org.openhab.core.io.net.http.HttpClientFactory;
2425
import org.openhab.core.thing.Bridge;
2526
import org.openhab.core.thing.Thing;
@@ -70,6 +71,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
7071
return new EaseeMasterChargerHandler(thing);
7172
} else if (THING_TYPE_CHARGER.equals(thingTypeUID)) {
7273
return new EaseeChargerHandler(thing);
74+
} else if (THING_TYPE_USER.equals(thingTypeUID)) {
75+
return new EaseeUserHandler(thing);
7376
} else {
7477
logger.warn("Unsupported Thing-Type: {}", thingTypeUID.getAsString());
7578
}

bundles/org.openhab.binding.easee/src/main/java/org/openhab/binding/easee/internal/command/AbstractCommand.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
import com.google.gson.Gson;
4343
import com.google.gson.GsonBuilder;
44+
import com.google.gson.JsonElement;
4445
import com.google.gson.JsonObject;
4546
import com.google.gson.ToNumberPolicy;
4647

@@ -127,10 +128,8 @@ public AbstractCommand(EaseeThingHandler handler, RetryOnFailure retryOnFailure,
127128
@Override
128129
public final void onSuccess(Response response) {
129130
super.onSuccess(response);
130-
if (response != null) {
131-
communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
132-
logger.debug("HTTP response {}", response.getStatus());
133-
}
131+
communicationStatus.setHttpCode(HttpStatus.getCode(response.getStatus()));
132+
logger.debug("HTTP response {}", response.getStatus());
134133
}
135134

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

171171
/**
@@ -192,7 +192,7 @@ public void onComplete(@Nullable Result result) {
192192
* @param json
193193
*/
194194
protected void onCompleteCodeOk(@Nullable String json) {
195-
JsonObject jsonObject = transform(json);
195+
JsonObject jsonObject = transform(json, JsonObject.class);
196196
if (jsonObject != null) {
197197
logger.debug("success");
198198
handler.updateChannelStatus(transformer.transform(jsonObject, getChannelGroup()));
@@ -206,7 +206,7 @@ protected void onCompleteCodeOk(@Nullable String json) {
206206
* @param json
207207
*/
208208
protected void onCompleteCodeDefault(@Nullable String json) {
209-
JsonObject jsonObject = transform(json);
209+
JsonObject jsonObject = transform(json, JsonObject.class);
210210
if (jsonObject == null) {
211211
jsonObject = new JsonObject();
212212
}
@@ -226,12 +226,13 @@ protected void onCompleteCodeDefault(@Nullable String json) {
226226
* error safe json transformer.
227227
*
228228
* @param json
229+
* @param clazz target type
229230
* @return
230231
*/
231-
protected @Nullable JsonObject transform(@Nullable String json) {
232+
protected <T extends JsonElement> @Nullable T transform(@Nullable String json, Class<T> clazz) {
232233
if (json != null) {
233234
try {
234-
return gson.fromJson(json, JsonObject.class);
235+
return gson.fromJson(json, clazz);
235236
} catch (Exception ex) {
236237
logger.debug("JSON could not be parsed: {}\nError: {}", json, ex.getMessage());
237238
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.easee.internal.command.account;
14+
15+
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
16+
17+
import org.eclipse.jdt.annotation.NonNullByDefault;
18+
import org.eclipse.jdt.annotation.Nullable;
19+
import org.eclipse.jetty.client.api.Request;
20+
import org.eclipse.jetty.http.HttpMethod;
21+
import org.openhab.binding.easee.internal.command.AbstractCommand;
22+
import org.openhab.binding.easee.internal.command.JsonResultProcessor;
23+
import org.openhab.binding.easee.internal.handler.EaseeThingHandler;
24+
25+
import com.google.gson.JsonArray;
26+
import com.google.gson.JsonElement;
27+
import com.google.gson.JsonObject;
28+
29+
/**
30+
* implements the get yearly consumption by user api call and computes a total sum.
31+
*
32+
* @author Alexander Friese - initial contribution
33+
*/
34+
@NonNullByDefault
35+
public class GetUserTotalConsumption extends AbstractCommand {
36+
37+
private final String url;
38+
39+
public GetUserTotalConsumption(EaseeThingHandler handler, String userId, JsonResultProcessor resultProcessor) {
40+
// retry does not make much sense as it is a polling command, command should always succeed therefore update
41+
// handler on failure.
42+
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES, resultProcessor);
43+
String siteId = handler.getBridgeConfiguration().getSiteId();
44+
this.url = USER_TOTAL_CONSUMPTION_URL.replaceAll("\\{siteId\\}", siteId).replaceAll("\\{userId\\}", userId);
45+
}
46+
47+
@Override
48+
protected Request prepareRequest(Request requestToPrepare) {
49+
requestToPrepare.method(HttpMethod.GET);
50+
return requestToPrepare;
51+
}
52+
53+
@Override
54+
protected String getURL() {
55+
return url;
56+
}
57+
58+
@Override
59+
protected String getChannelGroup() {
60+
return CHANNEL_GROUP_USER_CONSUMPTION;
61+
}
62+
63+
@Override
64+
protected void onCompleteCodeOk(@Nullable String json) {
65+
JsonArray yearlyData = transform(json, JsonArray.class);
66+
if (yearlyData != null) {
67+
double totalConsumption = 0;
68+
for (JsonElement element : yearlyData) {
69+
JsonObject yearEntry = element.getAsJsonObject();
70+
if (yearEntry.has(JSON_KEY_TOTAL_ENERGY_USAGE)) {
71+
totalConsumption += yearEntry.get(JSON_KEY_TOTAL_ENERGY_USAGE).getAsDouble();
72+
}
73+
}
74+
75+
JsonObject jsonObject = new JsonObject();
76+
jsonObject.addProperty(CHANNEL_USER_TOTAL_CONSUMPTION, totalConsumption);
77+
handler.updateChannelStatus(transformer.transform(jsonObject, getChannelGroup()));
78+
processResult(jsonObject);
79+
}
80+
}
81+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) 2010-2026 Contributors to the openHAB project
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*/
13+
package org.openhab.binding.easee.internal.command.site;
14+
15+
import static org.openhab.binding.easee.internal.EaseeBindingConstants.*;
16+
17+
import org.eclipse.jdt.annotation.NonNullByDefault;
18+
import org.eclipse.jetty.client.api.Request;
19+
import org.eclipse.jetty.http.HttpMethod;
20+
import org.openhab.binding.easee.internal.command.AbstractCommand;
21+
import org.openhab.binding.easee.internal.command.JsonResultProcessor;
22+
import org.openhab.binding.easee.internal.handler.EaseeBridgeHandler;
23+
24+
/**
25+
* implements the get site users api call.
26+
*
27+
* @author Alexander Friese - initial contribution
28+
*/
29+
@NonNullByDefault
30+
public class GetSiteUsers extends AbstractCommand {
31+
32+
public GetSiteUsers(EaseeBridgeHandler handler, JsonResultProcessor resultProcessor) {
33+
// retry does not make much sense as it is a polling command, command should always succeed therefore update
34+
// handler on failure.
35+
super(handler, RetryOnFailure.NO, ProcessFailureResponse.YES, resultProcessor);
36+
}
37+
38+
@Override
39+
protected Request prepareRequest(Request requestToPrepare) {
40+
requestToPrepare.method(HttpMethod.GET);
41+
return requestToPrepare;
42+
}
43+
44+
@Override
45+
protected String getURL() {
46+
String url = GET_SITE_USERS_URL;
47+
return url.replaceAll("\\{siteId\\}", handler.getBridgeConfiguration().getSiteId());
48+
}
49+
50+
@Override
51+
protected String getChannelGroup() {
52+
return CHANNEL_GROUP_SITE_INFO;
53+
}
54+
}

bundles/org.openhab.binding.easee/src/main/java/org/openhab/binding/easee/internal/command/site/SiteState.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ protected String getChannelGroup() {
7878
*/
7979
@Override
8080
protected void onCompleteCodeOk(@Nullable String json) {
81-
JsonObject jsonObject = transform(json);
81+
JsonObject jsonObject = transform(json, JsonObject.class);
8282

8383
if (jsonObject != null) {
8484
logger.debug("success");

bundles/org.openhab.binding.easee/src/main/java/org/openhab/binding/easee/internal/discovery/EaseeSiteDiscoveryService.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.openhab.binding.easee.internal.EaseeBindingConstants;
2020
import org.openhab.binding.easee.internal.Utils;
2121
import org.openhab.binding.easee.internal.command.site.GetSite;
22+
import org.openhab.binding.easee.internal.command.site.GetSiteUsers;
2223
import org.openhab.binding.easee.internal.connector.CommunicationStatus;
2324
import org.openhab.binding.easee.internal.handler.EaseeSiteHandler;
2425
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
@@ -53,6 +54,7 @@ public EaseeSiteDiscoveryService() throws IllegalArgumentException {
5354
@Override
5455
protected void startScan() {
5556
thingHandler.enqueueCommand(new GetSite(thingHandler, this::processSiteDiscoveryResult));
57+
thingHandler.enqueueCommand(new GetSiteUsers(thingHandler, this::processSiteUsersDiscoveryResult));
5658
}
5759

5860
@Override
@@ -153,4 +155,54 @@ private DiscoveryResultBuilder initDiscoveryResultBuilder(String deviceType, Str
153155
return DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID).withLabel(label)
154156
.withProperty(THING_CONFIG_ID, deviceId).withRepresentationProperty(THING_CONFIG_ID);
155157
}
158+
159+
/**
160+
* callback that handles json result data for site users discovery.
161+
*
162+
* @param status
163+
* @param siteUsers
164+
*/
165+
private void processSiteUsersDiscoveryResult(CommunicationStatus status, JsonObject siteUsers) {
166+
logger.debug("processSiteUsersDiscoveryResult {}", siteUsers);
167+
168+
JsonArray users = siteUsers.getAsJsonArray(JSON_KEY_SITE_USERS);
169+
if (users == null) {
170+
logger.info("Site users discovery failed, no users found.");
171+
} else {
172+
users.forEach(this::handleUserDiscovery);
173+
}
174+
}
175+
176+
/**
177+
* handles each user discovery result.
178+
*
179+
* @param user
180+
*/
181+
private void handleUserDiscovery(JsonElement json) {
182+
logger.debug("handleUserDiscovery {}", json);
183+
184+
JsonObject user = json.getAsJsonObject();
185+
String userId = Utils.getAsString(user, JSON_KEY_USER_ID);
186+
String userName = Utils.getAsString(user, JSON_KEY_GENERIC_NAME);
187+
String userEmail = Utils.getAsString(user, JSON_KEY_USER_EMAIL);
188+
String userPhoneNumber = Utils.getAsString(user, JSON_KEY_USER_PHONE_NUMBER);
189+
190+
if (userId != null) {
191+
String userLabel = userName != null ? userName : (userEmail != null ? userEmail : userId);
192+
193+
DiscoveryResultBuilder builder = initDiscoveryResultBuilder(DEVICE_USER, userId, userLabel);
194+
195+
if (userEmail != null) {
196+
builder.withProperty(JSON_KEY_USER_EMAIL, userEmail);
197+
}
198+
if (userName != null) {
199+
builder.withProperty(JSON_KEY_GENERIC_NAME, userName);
200+
}
201+
if (userPhoneNumber != null) {
202+
builder.withProperty(JSON_KEY_USER_PHONE_NUMBER, userPhoneNumber);
203+
}
204+
205+
thingDiscovered(builder.build());
206+
}
207+
}
156208
}

0 commit comments

Comments
 (0)