]> git.basschouten.com Git - openhab-addons.git/blob
39dfd9fdbfcaa6cb6ca3664cdf243c414e80c5aa
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.goecharger.internal.handler;
14
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;
36
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;
44
45 import javax.measure.quantity.ElectricCurrent;
46 import javax.measure.quantity.Energy;
47
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;
74
75 import com.google.gson.Gson;
76 import com.google.gson.JsonSyntaxException;
77
78 /**
79  * The {@link GoEChargerHandler} is responsible for handling commands, which are
80  * sent to one of the channels.
81  *
82  * @author Samuel Brucksch - Initial contribution
83  */
84 @NonNullByDefault
85 public class GoEChargerHandler extends BaseThingHandler {
86
87     private final Logger logger = LoggerFactory.getLogger(GoEChargerHandler.class);
88
89     private @Nullable GoEChargerConfiguration config;
90
91     private List<String> allChannels = new ArrayList<>();
92
93     private final Gson gson = new Gson();
94
95     private @Nullable ScheduledFuture<?> refreshJob;
96
97     private final HttpClient httpClient;
98
99     public GoEChargerHandler(Thing thing, HttpClient httpClient) {
100         super(thing);
101         this.httpClient = httpClient;
102     }
103
104     private State getValue(String channelId, GoEStatusResponseDTO goeResponse) {
105         switch (channelId) {
106             case MAX_CURRENT:
107                 if (goeResponse.maxCurrent == null) {
108                     return UnDefType.UNDEF;
109                 }
110                 return new QuantityType<>(goeResponse.maxCurrent, Units.AMPERE);
111             case PWM_SIGNAL:
112                 if (goeResponse.pwmSignal == null) {
113                     return UnDefType.UNDEF;
114                 }
115                 String pwmSignal = null;
116                 switch (goeResponse.pwmSignal) {
117                     case 1:
118                         pwmSignal = "READY_NO_CAR";
119                         break;
120                     case 2:
121                         pwmSignal = "CHARGING";
122                         break;
123                     case 3:
124                         pwmSignal = "WAITING_FOR_CAR";
125                         break;
126                     case 4:
127                         pwmSignal = "CHARGING_DONE_CAR_CONNECTED";
128                         break;
129                     default:
130                 }
131                 return new StringType(pwmSignal);
132             case ERROR:
133                 if (goeResponse.errorCode == null) {
134                     return UnDefType.UNDEF;
135                 }
136                 String error = null;
137                 switch (goeResponse.errorCode) {
138                     case 0:
139                         error = "NONE";
140                         break;
141                     case 1:
142                         error = "RCCB";
143                         break;
144                     case 3:
145                         error = "PHASE";
146                         break;
147                     case 8:
148                         error = "NO_GROUND";
149                         break;
150                     default:
151                         error = "INTERNAL";
152                         break;
153                 }
154                 return new StringType(error);
155             case ACCESS_CONFIGURATION:
156                 if (goeResponse.accessConfiguration == null) {
157                     return UnDefType.UNDEF;
158                 }
159                 String accessConfiguration = null;
160                 switch (goeResponse.accessConfiguration) {
161                     case 0:
162                         accessConfiguration = "OPEN";
163                         break;
164                     case 1:
165                         accessConfiguration = "RFID";
166                         break;
167                     case 2:
168                         accessConfiguration = "AWATTAR";
169                         break;
170                     case 3:
171                         accessConfiguration = "TIMER";
172                         break;
173                     default:
174                 }
175                 return new StringType(accessConfiguration);
176             case ALLOW_CHARGING:
177                 if (goeResponse.allowCharging == null) {
178                     return UnDefType.UNDEF;
179                 }
180                 return goeResponse.allowCharging == 1 ? OnOffType.ON : OnOffType.OFF;
181             case CABLE_ENCODING:
182                 if (goeResponse.cableEncoding == null) {
183                     return UnDefType.UNDEF;
184                 }
185                 return new QuantityType<>(goeResponse.cableEncoding, Units.AMPERE);
186             case PHASES:
187                 if (goeResponse.energy == null) {
188                     return UnDefType.UNDEF;
189                 }
190                 int count = 0;
191                 if (goeResponse.energy[4] > 0) { // current P1
192                     count++;
193                 }
194                 if (goeResponse.energy[5] > 0) { // current P2
195                     count++;
196                 }
197                 if (goeResponse.energy[6] > 0) { // current P3
198                     count++;
199                 }
200                 return new DecimalType(count);
201             case TEMPERATURE:
202                 if (goeResponse.temperature == null) {
203                     return UnDefType.UNDEF;
204                 }
205                 return new QuantityType<>(goeResponse.temperature, SIUnits.CELSIUS);
206             case SESSION_CHARGE_CONSUMPTION:
207                 if (goeResponse.sessionChargeConsumption == null) {
208                     return UnDefType.UNDEF;
209                 }
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;
215                 }
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;
221                 }
222                 return new QuantityType<>((Double) (goeResponse.totalChargeConsumption / 10d), Units.KILOWATT_HOUR);
223             case FIRMWARE:
224                 if (goeResponse.firmware == null) {
225                     return UnDefType.UNDEF;
226                 }
227                 return new StringType(goeResponse.firmware);
228             case VOLTAGE_L1:
229                 if (goeResponse.energy == null) {
230                     return UnDefType.UNDEF;
231                 }
232                 return new QuantityType<>(goeResponse.energy[0], Units.VOLT);
233             case VOLTAGE_L2:
234                 if (goeResponse.energy == null) {
235                     return UnDefType.UNDEF;
236                 }
237                 return new QuantityType<>(goeResponse.energy[1], Units.VOLT);
238             case VOLTAGE_L3:
239                 if (goeResponse.energy == null) {
240                     return UnDefType.UNDEF;
241                 }
242                 return new QuantityType<>(goeResponse.energy[2], Units.VOLT);
243             case CURRENT_L1:
244                 if (goeResponse.energy == null) {
245                     return UnDefType.UNDEF;
246                 }
247                 // values come in as A*10, 41 means 4.1A
248                 return new QuantityType<>((Double) (goeResponse.energy[4] / 10d), Units.AMPERE);
249             case CURRENT_L2:
250                 if (goeResponse.energy == null) {
251                     return UnDefType.UNDEF;
252                 }
253                 return new QuantityType<>((Double) (goeResponse.energy[5] / 10d), Units.AMPERE);
254             case CURRENT_L3:
255                 if (goeResponse.energy == null) {
256                     return UnDefType.UNDEF;
257                 }
258                 return new QuantityType<>((Double) (goeResponse.energy[6] / 10d), Units.AMPERE);
259             case POWER_L1:
260                 if (goeResponse.energy == null) {
261                     return UnDefType.UNDEF;
262                 }
263                 // values come in as kW*10, 41 means 4.1kW
264                 return new QuantityType<>(goeResponse.energy[7] * 100, Units.WATT);
265             case POWER_L2:
266                 if (goeResponse.energy == null) {
267                     return UnDefType.UNDEF;
268                 }
269                 return new QuantityType<>(goeResponse.energy[8] * 100, Units.WATT);
270             case POWER_L3:
271                 if (goeResponse.energy == null) {
272                     return UnDefType.UNDEF;
273                 }
274                 return new QuantityType<>(goeResponse.energy[9] * 100, Units.WATT);
275         }
276         return UnDefType.UNDEF;
277     }
278
279     @Override
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
283             // anyways
284             return;
285         }
286
287         String key = null;
288         String value = null;
289         switch (channelUID.getId()) {
290             case MAX_CURRENT:
291                 key = "amp";
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());
296                 }
297                 break;
298             case SESSION_CHARGE_CONSUMPTION_LIMIT:
299                 key = "dwo";
300                 if (command instanceof DecimalType) {
301                     value = String.valueOf(((DecimalType) command).intValue() * 10);
302                 } else if (command instanceof QuantityType<?>) {
303                     value = String
304                             .valueOf(((QuantityType<Energy>) command).toUnit(Units.KILOWATT_HOUR).intValue() * 10);
305                 }
306                 break;
307             case ALLOW_CHARGING:
308                 key = "alw";
309                 if (command instanceof OnOffType) {
310                     value = command == OnOffType.ON ? "1" : "0";
311                 }
312                 break;
313             case ACCESS_CONFIGURATION:
314                 key = "ast";
315                 if (command instanceof StringType) {
316                     switch (command.toString().toUpperCase()) {
317                         case "OPEN":
318                             value = "0";
319                             break;
320                         case "RFID":
321                             value = "1";
322                             break;
323                         case "AWATTAR":
324                             value = "2";
325                             break;
326                         case "TIMER":
327                             value = "3";
328                             break;
329                         default:
330                     }
331                 }
332                 break;
333             default:
334         }
335         if (key != null && value != null) {
336             sendData(key, value);
337         } else {
338             logger.warn("Could not update channel {} with key {} and value {}", channelUID.getId(), key, value);
339         }
340     }
341
342     @Override
343     public void initialize() {
344         config = getConfigAs(GoEChargerConfiguration.class);
345         allChannels = getThing().getChannels().stream().map(channel -> channel.getUID().getId())
346                 .collect(Collectors.toList());
347
348         updateStatus(ThingStatus.UNKNOWN);
349
350         startAutomaticRefresh();
351         logger.debug("Finished initializing!");
352     }
353
354     private String getUrl(String type) {
355         return type.replace("%IP%", StringUtils.trimToEmpty(config.ip));
356     }
357
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);
361
362         try {
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());
371         }
372     }
373
374     /**
375      * Request new data from Go-E charger
376      *
377      * @return the Go-E charger object mapping the JSON response or null in case of
378      *         error
379      * @throws ExecutionException
380      * @throws TimeoutException
381      * @throws InterruptedException
382      */
383     @Nullable
384     private GoEStatusResponseDTO getGoEData()
385             throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
386         String urlStr = getUrl(GoEChargerBindingConstants.API_URL);
387         logger.debug("GET URL = {}", urlStr);
388
389         ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
390                 .timeout(5, TimeUnit.SECONDS).send();
391
392         String response = contentResponse.getContentAsString();
393         logger.debug("GET Response: {}", response);
394         return gson.fromJson(response, GoEStatusResponseDTO.class);
395     }
396
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));
401         } else {
402             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
403             allChannels.forEach(channel -> updateState(channel, getValue(channel, goeResponse)));
404         }
405     }
406
407     private void refresh() {
408         // Request new GoE data
409         try {
410             GoEStatusResponseDTO goeResponse = getGoEData();
411             updateChannelsAndStatus(goeResponse, null);
412         } catch (InterruptedException | TimeoutException | ExecutionException e) {
413             updateChannelsAndStatus(null, e.getMessage());
414         }
415     }
416
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);
423         }
424     }
425
426     @Override
427     public void dispose() {
428         logger.debug("Disposing the Go-E Charger handler.");
429
430         final ScheduledFuture<?> refreshJob = this.refreshJob;
431         if (refreshJob != null && !refreshJob.isCancelled()) {
432             refreshJob.cancel(true);
433             this.refreshJob = null;
434         }
435     }
436 }