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