2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.goecharger.internal.handler;
15 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.*;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
21 import javax.measure.quantity.ElectricCurrent;
22 import javax.measure.quantity.Energy;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.eclipse.jetty.client.api.ContentResponse;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.openhab.binding.goecharger.internal.GoEChargerBindingConstants;
30 import org.openhab.binding.goecharger.internal.api.GoEStatusResponseBaseDTO;
31 import org.openhab.binding.goecharger.internal.api.GoEStatusResponseDTO;
32 import org.openhab.binding.goecharger.internal.api.GoEStatusResponseV2DTO;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.library.unit.SIUnits;
38 import org.openhab.core.library.unit.Units;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
50 import com.google.gson.JsonSyntaxException;
51 import com.google.gson.annotations.SerializedName;
54 * The {@link GoEChargerV2Handler} is responsible for handling commands, which are
55 * sent to one of the channels.
57 * @author Samuel Brucksch - Initial contribution
58 * @author Reinhard Plaim - Adapt to use API version 2
61 public class GoEChargerV2Handler extends GoEChargerBaseHandler {
63 private final Logger logger = LoggerFactory.getLogger(GoEChargerV2Handler.class);
65 private String filter = "";
67 public GoEChargerV2Handler(Thing thing, HttpClient httpClient) {
68 super(thing, httpClient);
71 @SuppressWarnings("PMD.SimplifyBooleanExpressions")
73 protected State getValue(String channelId, GoEStatusResponseBaseDTO goeResponseBase) {
74 var state = super.getValue(channelId, goeResponseBase);
75 if (state != UnDefType.UNDEF) {
79 var goeResponse = (GoEStatusResponseV2DTO) goeResponseBase;
82 if (goeResponse.phases == null) {
83 return UnDefType.UNDEF;
86 if (goeResponse.phases == 2) {
89 return new DecimalType(phases);
91 if (goeResponse.pwmSignal == null) {
92 return UnDefType.UNDEF;
94 String pwmSignal = null;
95 switch (goeResponse.pwmSignal) {
97 pwmSignal = "UNKNOWN/ERROR";
102 pwmSignal = "CHARGING";
105 pwmSignal = "WAITING_FOR_CAR";
108 pwmSignal = "COMPLETE";
114 return new StringType(pwmSignal);
116 if (goeResponse.errorCode == null) {
117 return UnDefType.UNDEF;
120 switch (goeResponse.errorCode) {
122 error = "UNKNOWN/ERROR";
130 error = "WAITING_FOR_CAR";
139 return new StringType(error);
141 if (goeResponse.transaction == null) {
142 return UnDefType.UNDEF;
144 return new DecimalType(goeResponse.transaction);
146 return OnOffType.from(goeResponse.allowCharging);
147 case TEMPERATURE_TYPE2_PORT:
148 // It was reported that the temperature is invalid when only one value is returned
149 // That's why it is checked that at least 2 values are returned
150 if (goeResponse.temperatures == null || goeResponse.temperatures.length < 2) {
151 return UnDefType.UNDEF;
153 return new QuantityType<>(goeResponse.temperatures[0], SIUnits.CELSIUS);
154 case TEMPERATURE_CIRCUIT_BOARD:
155 if (goeResponse.temperatures == null || goeResponse.temperatures.length < 2) {
156 return UnDefType.UNDEF;
158 return new QuantityType<>(goeResponse.temperatures[1], SIUnits.CELSIUS);
159 case SESSION_CHARGE_CONSUMPTION:
160 if (goeResponse.sessionChargeConsumption == null) {
161 return UnDefType.UNDEF;
163 return new QuantityType<>(goeResponse.sessionChargeConsumption / 1000d, Units.KILOWATT_HOUR);
164 case SESSION_CHARGE_CONSUMPTION_LIMIT:
165 if (goeResponse.sessionChargeConsumptionLimit == null) {
166 return UnDefType.UNDEF;
168 return new QuantityType<>(goeResponse.sessionChargeConsumptionLimit / 1000d, Units.KILOWATT_HOUR);
169 case TOTAL_CONSUMPTION:
170 if (goeResponse.totalChargeConsumption == null) {
171 return UnDefType.UNDEF;
173 return new QuantityType<>(goeResponse.totalChargeConsumption / 1000d, Units.KILOWATT_HOUR);
175 if (goeResponse.energy == null || goeResponse.energy.length < 1) {
176 return UnDefType.UNDEF;
178 return new QuantityType<>(goeResponse.energy[0], Units.VOLT);
180 if (goeResponse.energy == null || goeResponse.energy.length < 2) {
181 return UnDefType.UNDEF;
183 return new QuantityType<>(goeResponse.energy[1], Units.VOLT);
185 if (goeResponse.energy == null || goeResponse.energy.length < 3) {
186 return UnDefType.UNDEF;
188 return new QuantityType<>(goeResponse.energy[2], Units.VOLT);
190 if (goeResponse.energy == null || goeResponse.energy.length < 5) {
191 return UnDefType.UNDEF;
193 return new QuantityType<>(goeResponse.energy[4], Units.AMPERE);
195 if (goeResponse.energy == null || goeResponse.energy.length < 6) {
196 return UnDefType.UNDEF;
198 return new QuantityType<>(goeResponse.energy[5], Units.AMPERE);
200 if (goeResponse.energy == null || goeResponse.energy.length < 7) {
201 return UnDefType.UNDEF;
203 return new QuantityType<>(goeResponse.energy[6], Units.AMPERE);
205 if (goeResponse.energy == null || goeResponse.energy.length < 8) {
206 return UnDefType.UNDEF;
208 return new QuantityType<>(goeResponse.energy[7], Units.WATT);
210 if (goeResponse.energy == null || goeResponse.energy.length < 9) {
211 return UnDefType.UNDEF;
213 return new QuantityType<>(goeResponse.energy[8], Units.WATT);
215 if (goeResponse.energy == null || goeResponse.energy.length < 10) {
216 return UnDefType.UNDEF;
218 return new QuantityType<>(goeResponse.energy[9], Units.WATT);
220 if (goeResponse.energy == null || goeResponse.energy.length < 12) {
221 return UnDefType.UNDEF;
223 return new QuantityType<>(goeResponse.energy[11], Units.WATT);
225 if (goeResponse.forceState == null) {
226 return UnDefType.UNDEF;
228 return new DecimalType(goeResponse.forceState.toString());
230 return UnDefType.UNDEF;
234 public void handleCommand(ChannelUID channelUID, Command command) {
235 if (command instanceof RefreshType) {
236 // we can not update single channels and refresh is triggered automatically
244 switch (channelUID.getId()) {
247 if (command instanceof DecimalType decimalCommand) {
248 value = String.valueOf(decimalCommand.intValue());
249 } else if (command instanceof QuantityType<?>) {
250 value = String.valueOf(((QuantityType<ElectricCurrent>) command).toUnit(Units.AMPERE).intValue());
253 case SESSION_CHARGE_CONSUMPTION_LIMIT:
255 var multiplier = 1000;
256 if (command instanceof DecimalType decimalCommand) {
257 value = String.valueOf(decimalCommand.intValue() * multiplier);
258 } else if (command instanceof QuantityType<?>) {
259 value = String.valueOf(
260 ((QuantityType<Energy>) command).toUnit(Units.KILOWATT_HOUR).intValue() * multiplier);
265 if (command instanceof DecimalType decimalCommand) {
267 if (decimalCommand.intValue() == 3) {
268 // set value 2 for 3 phases
271 value = String.valueOf(phases);
276 if (command instanceof DecimalType decimalCommand) {
277 value = String.valueOf(decimalCommand.intValue());
282 if (command instanceof DecimalType decimalCommand) {
283 value = String.valueOf(decimalCommand.intValue());
288 if (key != null && value != null) {
289 sendData(key, value);
291 logger.warn("Could not update channel {} with key {} and value {}", channelUID.getId(), key, value);
296 public void initialize() {
297 // only read needed parameters
299 var declaredFields = GoEStatusResponseV2DTO.class.getDeclaredFields();
300 var declaredFieldsBase = GoEStatusResponseV2DTO.class.getSuperclass().getDeclaredFields();
302 for (var field : declaredFields) {
303 filter += field.getAnnotation(SerializedName.class).value() + ",";
305 for (var field : declaredFieldsBase) {
306 filter += field.getAnnotation(SerializedName.class).value() + ",";
308 filter = filter.substring(0, filter.length() - 1);
313 private String getReadUrl() {
314 return GoEChargerBindingConstants.API_URL_V2.replace("%IP%", config.ip.toString()) + filter;
317 private String getWriteUrl(String key, String value) {
318 return GoEChargerBindingConstants.SET_URL_V2.replace("%IP%", config.ip.toString()).replace("%KEY%", key)
319 .replace("%VALUE%", value);
322 private void sendData(String key, String value) {
323 String urlStr = getWriteUrl(key, value);
324 logger.trace("POST URL = {}", urlStr);
327 HttpMethod httpMethod = HttpMethod.GET;
328 ContentResponse contentResponse = httpClient.newRequest(urlStr).method(httpMethod)
329 .timeout(5, TimeUnit.SECONDS).send();
330 String response = contentResponse.getContentAsString();
332 logger.trace("{} Response: {}", httpMethod.toString(), response);
334 var statusCode = contentResponse.getStatus();
335 if (!(statusCode == 200 || statusCode == 204)) {
336 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
337 "@text/unsuccessful.communication-error");
338 logger.debug("Could not send data, Response {}, StatusCode: {}", response, statusCode);
340 } catch (InterruptedException ie) {
341 Thread.currentThread().interrupt();
342 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ie.toString());
343 logger.debug("Could not send data: {}, {}", urlStr, ie.toString());
344 } catch (TimeoutException | ExecutionException | JsonSyntaxException e) {
345 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
346 logger.debug("Could not send data: {}, {}", urlStr, e.toString());
351 * Request new data from Go-eCharger
353 * @return the Go-eCharger object mapping the JSON response or null in case of
355 * @throws ExecutionException
356 * @throws TimeoutException
357 * @throws InterruptedException
361 protected GoEStatusResponseBaseDTO getGoEData()
362 throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
363 String urlStr = getReadUrl();
364 logger.trace("GET URL = {}", urlStr);
366 ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
367 .timeout(5, TimeUnit.SECONDS).send();
369 String response = contentResponse.getContentAsString();
370 logger.trace("GET Response: {}", response);
372 if (config.apiVersion == 1) {
373 return gson.fromJson(response, GoEStatusResponseDTO.class);
375 return gson.fromJson(response, GoEStatusResponseV2DTO.class);
379 protected void updateChannelsAndStatus(@Nullable GoEStatusResponseBaseDTO goeResponse, @Nullable String message) {
380 if (goeResponse == null) {
381 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, message);
382 allChannels.forEach(channel -> updateState(channel, UnDefType.UNDEF));
384 updateStatus(ThingStatus.ONLINE);
385 allChannels.forEach(channel -> updateState(channel, getValue(channel, goeResponse)));