2 * Copyright (c) 2010-2022 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.ACCESS_CONFIGURATION;
16 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.ALLOW_CHARGING;
17 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.CABLE_ENCODING;
18 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.CURRENT_L1;
19 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.CURRENT_L2;
20 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.CURRENT_L3;
21 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.ERROR;
22 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.FIRMWARE;
23 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.MAX_CURRENT;
24 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.PHASES;
25 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.POWER_L1;
26 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.POWER_L2;
27 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.POWER_L3;
28 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.PWM_SIGNAL;
29 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.SESSION_CHARGE_CONSUMPTION;
30 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.SESSION_CHARGE_CONSUMPTION_LIMIT;
31 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.TEMPERATURE;
32 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.TOTAL_CONSUMPTION;
33 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.VOLTAGE_L1;
34 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.VOLTAGE_L2;
35 import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.VOLTAGE_L3;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.ScheduledFuture;
41 import java.util.concurrent.TimeUnit;
42 import java.util.concurrent.TimeoutException;
43 import java.util.stream.Collectors;
45 import javax.measure.quantity.ElectricCurrent;
46 import javax.measure.quantity.Energy;
48 import org.apache.commons.lang3.StringUtils;
49 import org.eclipse.jdt.annotation.NonNullByDefault;
50 import org.eclipse.jdt.annotation.Nullable;
51 import org.eclipse.jetty.client.HttpClient;
52 import org.eclipse.jetty.client.api.ContentResponse;
53 import org.eclipse.jetty.http.HttpMethod;
54 import org.openhab.binding.goecharger.internal.GoEChargerBindingConstants;
55 import org.openhab.binding.goecharger.internal.GoEChargerConfiguration;
56 import org.openhab.binding.goecharger.internal.api.GoEStatusResponseDTO;
57 import org.openhab.core.library.types.DecimalType;
58 import org.openhab.core.library.types.OnOffType;
59 import org.openhab.core.library.types.QuantityType;
60 import org.openhab.core.library.types.StringType;
61 import org.openhab.core.library.unit.SIUnits;
62 import org.openhab.core.library.unit.Units;
63 import org.openhab.core.thing.ChannelUID;
64 import org.openhab.core.thing.Thing;
65 import org.openhab.core.thing.ThingStatus;
66 import org.openhab.core.thing.ThingStatusDetail;
67 import org.openhab.core.thing.binding.BaseThingHandler;
68 import org.openhab.core.types.Command;
69 import org.openhab.core.types.RefreshType;
70 import org.openhab.core.types.State;
71 import org.openhab.core.types.UnDefType;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
75 import com.google.gson.Gson;
76 import com.google.gson.JsonSyntaxException;
79 * The {@link GoEChargerHandler} is responsible for handling commands, which are
80 * sent to one of the channels.
82 * @author Samuel Brucksch - Initial contribution
85 public class GoEChargerHandler extends BaseThingHandler {
87 private final Logger logger = LoggerFactory.getLogger(GoEChargerHandler.class);
89 private @Nullable GoEChargerConfiguration config;
91 private List<String> allChannels = new ArrayList<>();
93 private final Gson gson = new Gson();
95 private @Nullable ScheduledFuture<?> refreshJob;
97 private final HttpClient httpClient;
99 public GoEChargerHandler(Thing thing, HttpClient httpClient) {
101 this.httpClient = httpClient;
104 private State getValue(String channelId, GoEStatusResponseDTO goeResponse) {
107 if (goeResponse.maxCurrent == null) {
108 return UnDefType.UNDEF;
110 return new QuantityType<>(goeResponse.maxCurrent, Units.AMPERE);
112 if (goeResponse.pwmSignal == null) {
113 return UnDefType.UNDEF;
115 String pwmSignal = null;
116 switch (goeResponse.pwmSignal) {
118 pwmSignal = "READY_NO_CAR";
121 pwmSignal = "CHARGING";
124 pwmSignal = "WAITING_FOR_CAR";
127 pwmSignal = "CHARGING_DONE_CAR_CONNECTED";
131 return new StringType(pwmSignal);
133 if (goeResponse.errorCode == null) {
134 return UnDefType.UNDEF;
137 switch (goeResponse.errorCode) {
154 return new StringType(error);
155 case ACCESS_CONFIGURATION:
156 if (goeResponse.accessConfiguration == null) {
157 return UnDefType.UNDEF;
159 String accessConfiguration = null;
160 switch (goeResponse.accessConfiguration) {
162 accessConfiguration = "OPEN";
165 accessConfiguration = "RFID";
168 accessConfiguration = "AWATTAR";
171 accessConfiguration = "TIMER";
175 return new StringType(accessConfiguration);
177 if (goeResponse.allowCharging == null) {
178 return UnDefType.UNDEF;
180 return goeResponse.allowCharging == 1 ? OnOffType.ON : OnOffType.OFF;
182 if (goeResponse.cableEncoding == null) {
183 return UnDefType.UNDEF;
185 return new QuantityType<>(goeResponse.cableEncoding, Units.AMPERE);
187 if (goeResponse.energy == null) {
188 return UnDefType.UNDEF;
191 if (goeResponse.energy[4] > 0) { // current P1
194 if (goeResponse.energy[5] > 0) { // current P2
197 if (goeResponse.energy[6] > 0) { // current P3
200 return new DecimalType(count);
202 if (goeResponse.temperature == null) {
203 return UnDefType.UNDEF;
205 return new QuantityType<>(goeResponse.temperature, SIUnits.CELSIUS);
206 case SESSION_CHARGE_CONSUMPTION:
207 if (goeResponse.sessionChargeConsumption == null) {
208 return UnDefType.UNDEF;
210 return new QuantityType<>((Double) (goeResponse.sessionChargeConsumption / 360000d),
211 Units.KILOWATT_HOUR);
212 case SESSION_CHARGE_CONSUMPTION_LIMIT:
213 if (goeResponse.sessionChargeConsumptionLimit == null) {
214 return UnDefType.UNDEF;
216 return new QuantityType<>((Double) (goeResponse.sessionChargeConsumptionLimit / 10d),
217 Units.KILOWATT_HOUR);
218 case TOTAL_CONSUMPTION:
219 if (goeResponse.totalChargeConsumption == null) {
220 return UnDefType.UNDEF;
222 return new QuantityType<>((Double) (goeResponse.totalChargeConsumption / 10d), Units.KILOWATT_HOUR);
224 if (goeResponse.firmware == null) {
225 return UnDefType.UNDEF;
227 return new StringType(goeResponse.firmware);
229 if (goeResponse.energy == null) {
230 return UnDefType.UNDEF;
232 return new QuantityType<>(goeResponse.energy[0], Units.VOLT);
234 if (goeResponse.energy == null) {
235 return UnDefType.UNDEF;
237 return new QuantityType<>(goeResponse.energy[1], Units.VOLT);
239 if (goeResponse.energy == null) {
240 return UnDefType.UNDEF;
242 return new QuantityType<>(goeResponse.energy[2], Units.VOLT);
244 if (goeResponse.energy == null) {
245 return UnDefType.UNDEF;
247 // values come in as A*10, 41 means 4.1A
248 return new QuantityType<>((Double) (goeResponse.energy[4] / 10d), Units.AMPERE);
250 if (goeResponse.energy == null) {
251 return UnDefType.UNDEF;
253 return new QuantityType<>((Double) (goeResponse.energy[5] / 10d), Units.AMPERE);
255 if (goeResponse.energy == null) {
256 return UnDefType.UNDEF;
258 return new QuantityType<>((Double) (goeResponse.energy[6] / 10d), Units.AMPERE);
260 if (goeResponse.energy == null) {
261 return UnDefType.UNDEF;
263 // values come in as kW*10, 41 means 4.1kW
264 return new QuantityType<>(goeResponse.energy[7] * 100, Units.WATT);
266 if (goeResponse.energy == null) {
267 return UnDefType.UNDEF;
269 return new QuantityType<>(goeResponse.energy[8] * 100, Units.WATT);
271 if (goeResponse.energy == null) {
272 return UnDefType.UNDEF;
274 return new QuantityType<>(goeResponse.energy[9] * 100, Units.WATT);
276 return UnDefType.UNDEF;
280 public void handleCommand(ChannelUID channelUID, Command command) {
281 if (command instanceof RefreshType) {
282 // we can not update single channels and refresh is triggered automatically
289 switch (channelUID.getId()) {
292 if (command instanceof DecimalType) {
293 value = String.valueOf(((DecimalType) command).intValue());
294 } else if (command instanceof QuantityType<?>) {
295 value = String.valueOf(((QuantityType<ElectricCurrent>) command).toUnit(Units.AMPERE).intValue());
298 case SESSION_CHARGE_CONSUMPTION_LIMIT:
300 if (command instanceof DecimalType) {
301 value = String.valueOf(((DecimalType) command).intValue() * 10);
302 } else if (command instanceof QuantityType<?>) {
304 .valueOf(((QuantityType<Energy>) command).toUnit(Units.KILOWATT_HOUR).intValue() * 10);
309 if (command instanceof OnOffType) {
310 value = command == OnOffType.ON ? "1" : "0";
313 case ACCESS_CONFIGURATION:
315 if (command instanceof StringType) {
316 switch (command.toString().toUpperCase()) {
335 if (key != null && value != null) {
336 sendData(key, value);
338 logger.warn("Could not update channel {} with key {} and value {}", channelUID.getId(), key, value);
343 public void initialize() {
344 config = getConfigAs(GoEChargerConfiguration.class);
345 allChannels = getThing().getChannels().stream().map(channel -> channel.getUID().getId())
346 .collect(Collectors.toList());
348 updateStatus(ThingStatus.UNKNOWN);
350 startAutomaticRefresh();
351 logger.debug("Finished initializing!");
354 private String getUrl(String type) {
355 return type.replace("%IP%", StringUtils.trimToEmpty(config.ip));
358 private void sendData(String key, String value) {
359 String urlStr = getUrl(GoEChargerBindingConstants.MQTT_URL).replace("%KEY%", key).replace("%VALUE%", value);
360 logger.debug("POST URL = {}", urlStr);
363 ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.POST)
364 .timeout(5, TimeUnit.SECONDS).send();
365 String response = contentResponse.getContentAsString();
366 logger.debug("POST Response: {}", response);
367 GoEStatusResponseDTO result = gson.fromJson(response, GoEStatusResponseDTO.class);
368 updateChannelsAndStatus(result, null);
369 } catch (InterruptedException | TimeoutException | ExecutionException | JsonSyntaxException e) {
370 updateChannelsAndStatus(null, e.getMessage());
375 * Request new data from Go-E charger
377 * @return the Go-E charger object mapping the JSON response or null in case of
379 * @throws ExecutionException
380 * @throws TimeoutException
381 * @throws InterruptedException
384 private GoEStatusResponseDTO getGoEData()
385 throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
386 String urlStr = getUrl(GoEChargerBindingConstants.API_URL);
387 logger.debug("GET URL = {}", urlStr);
389 ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
390 .timeout(5, TimeUnit.SECONDS).send();
392 String response = contentResponse.getContentAsString();
393 logger.debug("GET Response: {}", response);
394 return gson.fromJson(response, GoEStatusResponseDTO.class);
397 private void updateChannelsAndStatus(@Nullable GoEStatusResponseDTO goeResponse, @Nullable String message) {
398 if (goeResponse == null) {
399 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, message);
400 allChannels.forEach(channel -> updateState(channel, UnDefType.UNDEF));
402 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
403 allChannels.forEach(channel -> updateState(channel, getValue(channel, goeResponse)));
407 private void refresh() {
408 // Request new GoE data
410 GoEStatusResponseDTO goeResponse = getGoEData();
411 updateChannelsAndStatus(goeResponse, null);
412 } catch (InterruptedException | TimeoutException | ExecutionException e) {
413 updateChannelsAndStatus(null, e.getMessage());
417 private void startAutomaticRefresh() {
418 if (refreshJob == null || refreshJob.isCancelled()) {
419 GoEChargerConfiguration config = getConfigAs(GoEChargerConfiguration.class);
420 int delay = config.refreshInterval.intValue();
421 logger.debug("Running refresh job with delay {} s", delay);
422 refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, delay, TimeUnit.SECONDS);
427 public void dispose() {
428 logger.debug("Disposing the Go-E Charger handler.");
430 final ScheduledFuture<?> refreshJob = this.refreshJob;
431 if (refreshJob != null && !refreshJob.isCancelled()) {
432 refreshJob.cancel(true);
433 this.refreshJob = null;