2 * Copyright (c) 2010-2020 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.lang.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.SmartHomeUnits;
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, SmartHomeUnits.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, SmartHomeUnits.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 SmartHomeUnits.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 SmartHomeUnits.KILOWATT_HOUR);
218 case TOTAL_CONSUMPTION:
219 if (goeResponse.totalChargeConsumption == null) {
220 return UnDefType.UNDEF;
222 return new QuantityType<>((Double) (goeResponse.totalChargeConsumption / 10d),
223 SmartHomeUnits.KILOWATT_HOUR);
225 if (goeResponse.firmware == null) {
226 return UnDefType.UNDEF;
228 return new StringType(goeResponse.firmware);
230 if (goeResponse.energy == null) {
231 return UnDefType.UNDEF;
233 return new QuantityType<>(goeResponse.energy[0], SmartHomeUnits.VOLT);
235 if (goeResponse.energy == null) {
236 return UnDefType.UNDEF;
238 return new QuantityType<>(goeResponse.energy[1], SmartHomeUnits.VOLT);
240 if (goeResponse.energy == null) {
241 return UnDefType.UNDEF;
243 return new QuantityType<>(goeResponse.energy[2], SmartHomeUnits.VOLT);
245 if (goeResponse.energy == null) {
246 return UnDefType.UNDEF;
248 // values come in as A*10, 41 means 4.1A
249 return new QuantityType<>((Double) (goeResponse.energy[4] / 10d), SmartHomeUnits.AMPERE);
251 if (goeResponse.energy == null) {
252 return UnDefType.UNDEF;
254 return new QuantityType<>((Double) (goeResponse.energy[5] / 10d), SmartHomeUnits.AMPERE);
256 if (goeResponse.energy == null) {
257 return UnDefType.UNDEF;
259 return new QuantityType<>((Double) (goeResponse.energy[6] / 10d), SmartHomeUnits.AMPERE);
261 if (goeResponse.energy == null) {
262 return UnDefType.UNDEF;
264 // values come in as kW*10, 41 means 4.1kW
265 return new QuantityType<>(goeResponse.energy[7] * 100, SmartHomeUnits.WATT);
267 if (goeResponse.energy == null) {
268 return UnDefType.UNDEF;
270 return new QuantityType<>(goeResponse.energy[8] * 100, SmartHomeUnits.WATT);
272 if (goeResponse.energy == null) {
273 return UnDefType.UNDEF;
275 return new QuantityType<>(goeResponse.energy[9] * 100, SmartHomeUnits.WATT);
277 return UnDefType.UNDEF;
281 public void handleCommand(ChannelUID channelUID, Command command) {
282 if (command instanceof RefreshType) {
283 // we can not update single channels and refresh is triggered automatically
290 switch (channelUID.getId()) {
293 if (command instanceof DecimalType) {
294 value = String.valueOf(((DecimalType) command).intValue());
295 } else if (command instanceof QuantityType<?>) {
296 value = String.valueOf(
297 ((QuantityType<ElectricCurrent>) command).toUnit(SmartHomeUnits.AMPERE).intValue());
300 case SESSION_CHARGE_CONSUMPTION_LIMIT:
302 if (command instanceof DecimalType) {
303 value = String.valueOf(((DecimalType) command).intValue() * 10);
304 } else if (command instanceof QuantityType<?>) {
305 value = String.valueOf(
306 ((QuantityType<Energy>) command).toUnit(SmartHomeUnits.KILOWATT_HOUR).intValue() * 10);
311 if (command instanceof OnOffType) {
312 value = command == OnOffType.ON ? "1" : "0";
315 case ACCESS_CONFIGURATION:
317 if (command instanceof StringType) {
318 switch (command.toString().toUpperCase()) {
337 if (key != null && value != null) {
338 sendData(key, value);
340 logger.warn("Could not update channel {} with key {} and value {}", channelUID.getId(), key, value);
345 public void initialize() {
346 config = getConfigAs(GoEChargerConfiguration.class);
347 allChannels = getThing().getChannels().stream().map(channel -> channel.getUID().getId())
348 .collect(Collectors.toList());
350 updateStatus(ThingStatus.UNKNOWN);
352 startAutomaticRefresh();
353 logger.debug("Finished initializing!");
356 private String getUrl(String type) {
357 return type.replace("%IP%", StringUtils.trimToEmpty(config.ip));
360 private void sendData(String key, String value) {
361 String urlStr = getUrl(GoEChargerBindingConstants.MQTT_URL).replace("%KEY%", key).replace("%VALUE%", value);
362 logger.debug("POST URL = {}", urlStr);
365 ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.POST)
366 .timeout(5, TimeUnit.SECONDS).send();
367 String response = contentResponse.getContentAsString();
368 logger.debug("POST Response: {}", response);
369 GoEStatusResponseDTO result = gson.fromJson(response, GoEStatusResponseDTO.class);
370 updateChannelsAndStatus(result, null);
371 } catch (InterruptedException | TimeoutException | ExecutionException | JsonSyntaxException e) {
372 updateChannelsAndStatus(null, e.getMessage());
377 * Request new data from Go-E charger
379 * @return the Go-E charger object mapping the JSON response or null in case of
381 * @throws ExecutionException
382 * @throws TimeoutException
383 * @throws InterruptedException
386 private GoEStatusResponseDTO getGoEData()
387 throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
388 String urlStr = getUrl(GoEChargerBindingConstants.API_URL);
389 logger.debug("GET URL = {}", urlStr);
391 ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
392 .timeout(5, TimeUnit.SECONDS).send();
394 String response = contentResponse.getContentAsString();
395 logger.debug("GET Response: {}", response);
396 return gson.fromJson(response, GoEStatusResponseDTO.class);
399 private void updateChannelsAndStatus(@Nullable GoEStatusResponseDTO goeResponse, @Nullable String message) {
400 if (goeResponse == null) {
401 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, message);
402 allChannels.forEach(channel -> updateState(channel, UnDefType.UNDEF));
404 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
405 allChannels.forEach(channel -> updateState(channel, getValue(channel, goeResponse)));
409 private void refresh() {
410 // Request new GoE data
412 GoEStatusResponseDTO goeResponse = getGoEData();
413 updateChannelsAndStatus(goeResponse, null);
414 } catch (InterruptedException | TimeoutException | ExecutionException e) {
415 updateChannelsAndStatus(null, e.getMessage());
419 private void startAutomaticRefresh() {
420 if (refreshJob == null || refreshJob.isCancelled()) {
421 GoEChargerConfiguration config = getConfigAs(GoEChargerConfiguration.class);
422 int delay = config.refreshInterval.intValue();
423 logger.debug("Running refresh job with delay {} s", delay);
424 refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, delay, TimeUnit.SECONDS);
429 public void dispose() {
430 logger.debug("Disposing the Go-E Charger handler.");
432 final ScheduledFuture<?> refreshJob = this.refreshJob;
433 if (refreshJob != null && !refreshJob.isCancelled()) {
434 refreshJob.cancel(true);
435 this.refreshJob = null;