]> git.basschouten.com Git - openhab-addons.git/blob
5c75822e6e6f4babb2884b538b22f391f6583ad4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.*;
16
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20
21 import javax.measure.quantity.ElectricCurrent;
22 import javax.measure.quantity.Energy;
23
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;
49
50 import com.google.gson.JsonSyntaxException;
51 import com.google.gson.annotations.SerializedName;
52
53 /**
54  * The {@link GoEChargerV2Handler} is responsible for handling commands, which are
55  * sent to one of the channels.
56  *
57  * @author Samuel Brucksch - Initial contribution
58  * @author Reinhard Plaim - Adapt to use API version 2
59  */
60 @NonNullByDefault
61 public class GoEChargerV2Handler extends GoEChargerBaseHandler {
62
63     private final Logger logger = LoggerFactory.getLogger(GoEChargerV2Handler.class);
64
65     private String filter = "";
66
67     public GoEChargerV2Handler(Thing thing, HttpClient httpClient) {
68         super(thing, httpClient);
69     }
70
71     @Override
72     protected State getValue(String channelId, GoEStatusResponseBaseDTO goeResponseBase) {
73         var state = super.getValue(channelId, goeResponseBase);
74         if (state != UnDefType.UNDEF) {
75             return state;
76         }
77
78         var goeResponse = (GoEStatusResponseV2DTO) goeResponseBase;
79         switch (channelId) {
80             case PHASES:
81                 if (goeResponse.phases == null) {
82                     return UnDefType.UNDEF;
83                 }
84                 var phases = "1";
85                 if (goeResponse.phases == 2) {
86                     phases = "3";
87                 }
88                 return new DecimalType(phases);
89             case PWM_SIGNAL:
90                 if (goeResponse.pwmSignal == null) {
91                     return UnDefType.UNDEF;
92                 }
93                 String pwmSignal = null;
94                 switch (goeResponse.pwmSignal) {
95                     case 0:
96                         pwmSignal = "UNKNOWN/ERROR";
97                     case 1:
98                         pwmSignal = "IDLE";
99                         break;
100                     case 2:
101                         pwmSignal = "CHARGING";
102                         break;
103                     case 3:
104                         pwmSignal = "WAITING_FOR_CAR";
105                         break;
106                     case 4:
107                         pwmSignal = "COMPLETE";
108                         break;
109                     case 5:
110                         pwmSignal = "ERROR";
111                     default:
112                 }
113                 return new StringType(pwmSignal);
114             case ERROR:
115                 if (goeResponse.errorCode == null) {
116                     return UnDefType.UNDEF;
117                 }
118                 String error = null;
119                 switch (goeResponse.errorCode) {
120                     case 0:
121                         error = "UNKNOWN/ERROR";
122                     case 1:
123                         error = "IDLE";
124                         break;
125                     case 2:
126                         error = "CHARGING";
127                         break;
128                     case 3:
129                         error = "WAITING_FOR_CAR";
130                         break;
131                     case 4:
132                         error = "COMPLETE";
133                         break;
134                     case 5:
135                         error = "ERROR";
136                     default:
137                 }
138                 return new StringType(error);
139             case ALLOW_CHARGING:
140                 return goeResponse.allowCharging == true ? OnOffType.ON : OnOffType.OFF;
141             case TEMPERATURE_TYPE2_PORT:
142                 if (goeResponse.temperatures == null) {
143                     return UnDefType.UNDEF;
144                 }
145                 return new QuantityType<>(goeResponse.temperatures[0], SIUnits.CELSIUS);
146             case TEMPERATURE_CIRCUIT_BOARD:
147                 if (goeResponse.temperatures == null) {
148                     return UnDefType.UNDEF;
149                 }
150                 return new QuantityType<>(goeResponse.temperatures[1], SIUnits.CELSIUS);
151             case SESSION_CHARGE_CONSUMPTION:
152                 if (goeResponse.sessionChargeConsumption == null) {
153                     return UnDefType.UNDEF;
154                 }
155                 return new QuantityType<>(goeResponse.sessionChargeConsumption / 1000d, Units.KILOWATT_HOUR);
156             case SESSION_CHARGE_CONSUMPTION_LIMIT:
157                 if (goeResponse.sessionChargeConsumptionLimit == null) {
158                     return UnDefType.UNDEF;
159                 }
160                 return new QuantityType<>(goeResponse.sessionChargeConsumptionLimit / 1000d, Units.KILOWATT_HOUR);
161             case TOTAL_CONSUMPTION:
162                 if (goeResponse.totalChargeConsumption == null) {
163                     return UnDefType.UNDEF;
164                 }
165                 return new QuantityType<>((Double) (goeResponse.totalChargeConsumption / 1000d), Units.KILOWATT_HOUR);
166             case VOLTAGE_L1:
167                 if (goeResponse.energy == null) {
168                     return UnDefType.UNDEF;
169                 }
170                 return new QuantityType<>(goeResponse.energy[0], Units.VOLT);
171             case VOLTAGE_L2:
172                 if (goeResponse.energy == null) {
173                     return UnDefType.UNDEF;
174                 }
175                 return new QuantityType<>(goeResponse.energy[1], Units.VOLT);
176             case VOLTAGE_L3:
177                 if (goeResponse.energy == null) {
178                     return UnDefType.UNDEF;
179                 }
180                 return new QuantityType<>(goeResponse.energy[2], Units.VOLT);
181             case CURRENT_L1:
182                 if (goeResponse.energy == null) {
183                     return UnDefType.UNDEF;
184                 }
185                 return new QuantityType<>(goeResponse.energy[4], Units.AMPERE);
186             case CURRENT_L2:
187                 if (goeResponse.energy == null) {
188                     return UnDefType.UNDEF;
189                 }
190                 return new QuantityType<>(goeResponse.energy[5], Units.AMPERE);
191             case CURRENT_L3:
192                 if (goeResponse.energy == null) {
193                     return UnDefType.UNDEF;
194                 }
195                 return new QuantityType<>(goeResponse.energy[6], Units.AMPERE);
196             case POWER_L1:
197                 if (goeResponse.energy == null) {
198                     return UnDefType.UNDEF;
199                 }
200                 return new QuantityType<>(goeResponse.energy[7], Units.WATT);
201             case POWER_L2:
202                 if (goeResponse.energy == null) {
203                     return UnDefType.UNDEF;
204                 }
205                 return new QuantityType<>(goeResponse.energy[8], Units.WATT);
206             case POWER_L3:
207                 if (goeResponse.energy == null) {
208                     return UnDefType.UNDEF;
209                 }
210                 return new QuantityType<>(goeResponse.energy[9], Units.WATT);
211             case POWER_ALL:
212                 if (goeResponse.energy == null) {
213                     return UnDefType.UNDEF;
214                 }
215                 return new QuantityType<>(goeResponse.energy[11], Units.WATT);
216             case FORCE_STATE:
217                 if (goeResponse.forceState == null) {
218                     return UnDefType.UNDEF;
219                 }
220                 return new DecimalType(goeResponse.forceState.toString());
221         }
222         return UnDefType.UNDEF;
223     }
224
225     @Override
226     public void handleCommand(ChannelUID channelUID, Command command) {
227         if (command instanceof RefreshType) {
228             // we can not update single channels and refresh is triggered automatically
229             // anyways
230             return;
231         }
232
233         String key = null;
234         String value = null;
235
236         switch (channelUID.getId()) {
237             case MAX_CURRENT:
238                 key = "amp";
239                 if (command instanceof DecimalType) {
240                     value = String.valueOf(((DecimalType) command).intValue());
241                 } else if (command instanceof QuantityType<?>) {
242                     value = String.valueOf(((QuantityType<ElectricCurrent>) command).toUnit(Units.AMPERE).intValue());
243                 }
244                 break;
245             case SESSION_CHARGE_CONSUMPTION_LIMIT:
246                 key = "dwo";
247                 var multiplier = 1000;
248                 if (command instanceof DecimalType) {
249                     value = String.valueOf(((DecimalType) command).intValue() * multiplier);
250                 } else if (command instanceof QuantityType<?>) {
251                     value = String.valueOf(
252                             ((QuantityType<Energy>) command).toUnit(Units.KILOWATT_HOUR).intValue() * multiplier);
253                 }
254                 break;
255             case PHASES:
256                 key = "psm";
257                 if (command instanceof DecimalType) {
258                     var phases = 1;
259                     var help = (DecimalType) command;
260                     if (help.intValue() == 3) {
261                         // set value 2 for 3 phases
262                         phases = 2;
263                     }
264                     value = String.valueOf(phases);
265                 }
266                 break;
267             case FORCE_STATE:
268                 key = "frc";
269                 if (command instanceof DecimalType) {
270                     value = String.valueOf(((DecimalType) command).intValue());
271                 }
272         }
273
274         if (key != null && value != null) {
275             sendData(key, value);
276         } else {
277             logger.warn("Could not update channel {} with key {} and value {}", channelUID.getId(), key, value);
278         }
279     }
280
281     @Override
282     public void initialize() {
283         // only read needed parameters
284         filter = "?filter=";
285         var declaredFields = GoEStatusResponseV2DTO.class.getDeclaredFields();
286         var declaredFieldsBase = GoEStatusResponseV2DTO.class.getSuperclass().getDeclaredFields();
287
288         for (var field : declaredFields) {
289             filter += field.getAnnotation(SerializedName.class).value() + ",";
290         }
291         for (var field : declaredFieldsBase) {
292             filter += field.getAnnotation(SerializedName.class).value() + ",";
293         }
294         filter = filter.substring(0, filter.length() - 1);
295
296         super.initialize();
297     }
298
299     private String getReadUrl() {
300         return GoEChargerBindingConstants.API_URL_V2.replace("%IP%", config.ip.toString()) + filter;
301     }
302
303     private String getWriteUrl(String key, String value) {
304         return GoEChargerBindingConstants.SET_URL_V2.replace("%IP%", config.ip.toString()).replace("%KEY%", key)
305                 .replace("%VALUE%", value);
306     }
307
308     private void sendData(String key, String value) {
309         String urlStr = getWriteUrl(key, value);
310         logger.trace("POST URL = {}", urlStr);
311
312         try {
313             HttpMethod httpMethod = HttpMethod.GET;
314             ContentResponse contentResponse = httpClient.newRequest(urlStr).method(httpMethod)
315                     .timeout(5, TimeUnit.SECONDS).send();
316             String response = contentResponse.getContentAsString();
317
318             logger.trace("{} Response: {}", httpMethod.toString(), response);
319
320             var statusCode = contentResponse.getStatus();
321             if (!(statusCode == 200 || statusCode == 204)) {
322                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
323                         "@text/unsuccessful.communication-error");
324                 logger.debug("Could not send data, Response {}, StatusCode: {}", response, statusCode);
325             }
326         } catch (InterruptedException ie) {
327             Thread.currentThread().interrupt();
328             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ie.toString());
329             logger.debug("Could not send data: {}, {}", urlStr, ie.toString());
330         } catch (TimeoutException | ExecutionException | JsonSyntaxException e) {
331             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
332             logger.debug("Could not send data: {}, {}", urlStr, e.toString());
333         }
334     }
335
336     /**
337      * Request new data from Go-eCharger
338      *
339      * @return the Go-eCharger object mapping the JSON response or null in case of
340      *         error
341      * @throws ExecutionException
342      * @throws TimeoutException
343      * @throws InterruptedException
344      */
345     @Nullable
346     @Override
347     protected GoEStatusResponseBaseDTO getGoEData()
348             throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
349         String urlStr = getReadUrl();
350         logger.trace("GET URL = {}", urlStr);
351
352         ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
353                 .timeout(5, TimeUnit.SECONDS).send();
354
355         String response = contentResponse.getContentAsString();
356         logger.trace("GET Response: {}", response);
357
358         if (config.apiVersion == 1) {
359             return gson.fromJson(response, GoEStatusResponseDTO.class);
360         }
361         return gson.fromJson(response, GoEStatusResponseV2DTO.class);
362     }
363
364     protected void updateChannelsAndStatus(@Nullable GoEStatusResponseBaseDTO goeResponse, @Nullable String message) {
365         if (goeResponse == null) {
366             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, message);
367             allChannels.forEach(channel -> updateState(channel, UnDefType.UNDEF));
368         } else {
369             updateStatus(ThingStatus.ONLINE);
370             allChannels
371                     .forEach(channel -> updateState(channel, getValue(channel, (GoEStatusResponseV2DTO) goeResponse)));
372         }
373     }
374 }