]> git.basschouten.com Git - openhab-addons.git/blob
c9baeedc5c24e379023035d5623cdf72bf018652
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.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;
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, SmartHomeUnits.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, SmartHomeUnits.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                         SmartHomeUnits.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                         SmartHomeUnits.KILOWATT_HOUR);
218             case TOTAL_CONSUMPTION:
219                 if (goeResponse.totalChargeConsumption == null) {
220                     return UnDefType.UNDEF;
221                 }
222                 return new QuantityType<>((Double) (goeResponse.totalChargeConsumption / 10d),
223                         SmartHomeUnits.KILOWATT_HOUR);
224             case FIRMWARE:
225                 if (goeResponse.firmware == null) {
226                     return UnDefType.UNDEF;
227                 }
228                 return new StringType(goeResponse.firmware);
229             case VOLTAGE_L1:
230                 if (goeResponse.energy == null) {
231                     return UnDefType.UNDEF;
232                 }
233                 return new QuantityType<>(goeResponse.energy[0], SmartHomeUnits.VOLT);
234             case VOLTAGE_L2:
235                 if (goeResponse.energy == null) {
236                     return UnDefType.UNDEF;
237                 }
238                 return new QuantityType<>(goeResponse.energy[1], SmartHomeUnits.VOLT);
239             case VOLTAGE_L3:
240                 if (goeResponse.energy == null) {
241                     return UnDefType.UNDEF;
242                 }
243                 return new QuantityType<>(goeResponse.energy[2], SmartHomeUnits.VOLT);
244             case CURRENT_L1:
245                 if (goeResponse.energy == null) {
246                     return UnDefType.UNDEF;
247                 }
248                 // values come in as A*10, 41 means 4.1A
249                 return new QuantityType<>((Double) (goeResponse.energy[4] / 10d), SmartHomeUnits.AMPERE);
250             case CURRENT_L2:
251                 if (goeResponse.energy == null) {
252                     return UnDefType.UNDEF;
253                 }
254                 return new QuantityType<>((Double) (goeResponse.energy[5] / 10d), SmartHomeUnits.AMPERE);
255             case CURRENT_L3:
256                 if (goeResponse.energy == null) {
257                     return UnDefType.UNDEF;
258                 }
259                 return new QuantityType<>((Double) (goeResponse.energy[6] / 10d), SmartHomeUnits.AMPERE);
260             case POWER_L1:
261                 if (goeResponse.energy == null) {
262                     return UnDefType.UNDEF;
263                 }
264                 // values come in as kW*10, 41 means 4.1kW
265                 return new QuantityType<>(goeResponse.energy[7] * 100, SmartHomeUnits.WATT);
266             case POWER_L2:
267                 if (goeResponse.energy == null) {
268                     return UnDefType.UNDEF;
269                 }
270                 return new QuantityType<>(goeResponse.energy[8] * 100, SmartHomeUnits.WATT);
271             case POWER_L3:
272                 if (goeResponse.energy == null) {
273                     return UnDefType.UNDEF;
274                 }
275                 return new QuantityType<>(goeResponse.energy[9] * 100, SmartHomeUnits.WATT);
276         }
277         return UnDefType.UNDEF;
278     }
279
280     @Override
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
284             // anyways
285             return;
286         }
287
288         String key = null;
289         String value = null;
290         switch (channelUID.getId()) {
291             case MAX_CURRENT:
292                 key = "amp";
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());
298                 }
299                 break;
300             case SESSION_CHARGE_CONSUMPTION_LIMIT:
301                 key = "dwo";
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);
307                 }
308                 break;
309             case ALLOW_CHARGING:
310                 key = "alw";
311                 if (command instanceof OnOffType) {
312                     value = command == OnOffType.ON ? "1" : "0";
313                 }
314                 break;
315             case ACCESS_CONFIGURATION:
316                 key = "ast";
317                 if (command instanceof StringType) {
318                     switch (command.toString().toUpperCase()) {
319                         case "OPEN":
320                             value = "0";
321                             break;
322                         case "RFID":
323                             value = "1";
324                             break;
325                         case "AWATTAR":
326                             value = "2";
327                             break;
328                         case "TIMER":
329                             value = "3";
330                             break;
331                         default:
332                     }
333                 }
334                 break;
335             default:
336         }
337         if (key != null && value != null) {
338             sendData(key, value);
339         } else {
340             logger.warn("Could not update channel {} with key {} and value {}", channelUID.getId(), key, value);
341         }
342     }
343
344     @Override
345     public void initialize() {
346         config = getConfigAs(GoEChargerConfiguration.class);
347         allChannels = getThing().getChannels().stream().map(channel -> channel.getUID().getId())
348                 .collect(Collectors.toList());
349
350         updateStatus(ThingStatus.UNKNOWN);
351
352         startAutomaticRefresh();
353         logger.debug("Finished initializing!");
354     }
355
356     private String getUrl(String type) {
357         return type.replace("%IP%", StringUtils.trimToEmpty(config.ip));
358     }
359
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);
363
364         try {
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());
373         }
374     }
375
376     /**
377      * Request new data from Go-E charger
378      *
379      * @return the Go-E charger object mapping the JSON response or null in case of
380      *         error
381      * @throws ExecutionException
382      * @throws TimeoutException
383      * @throws InterruptedException
384      */
385     @Nullable
386     private GoEStatusResponseDTO getGoEData()
387             throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
388         String urlStr = getUrl(GoEChargerBindingConstants.API_URL);
389         logger.debug("GET URL = {}", urlStr);
390
391         ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
392                 .timeout(5, TimeUnit.SECONDS).send();
393
394         String response = contentResponse.getContentAsString();
395         logger.debug("GET Response: {}", response);
396         return gson.fromJson(response, GoEStatusResponseDTO.class);
397     }
398
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));
403         } else {
404             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
405             allChannels.forEach(channel -> updateState(channel, getValue(channel, goeResponse)));
406         }
407     }
408
409     private void refresh() {
410         // Request new GoE data
411         try {
412             GoEStatusResponseDTO goeResponse = getGoEData();
413             updateChannelsAndStatus(goeResponse, null);
414         } catch (InterruptedException | TimeoutException | ExecutionException e) {
415             updateChannelsAndStatus(null, e.getMessage());
416         }
417     }
418
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);
425         }
426     }
427
428     @Override
429     public void dispose() {
430         logger.debug("Disposing the Go-E Charger handler.");
431
432         final ScheduledFuture<?> refreshJob = this.refreshJob;
433         if (refreshJob != null && !refreshJob.isCancelled()) {
434             refreshJob.cancel(true);
435             this.refreshJob = null;
436         }
437     }
438 }