]> git.basschouten.com Git - openhab-addons.git/blob
248bc9498325eee50affefd4e96f41a8456f5820
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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 OnOffType.from(goeResponse.allowCharging);
147             case TEMPERATURE_TYPE2_PORT:
148                 // It was reported that the temperature is invalid when only one value is returned
149                 // That's why it is checked that at least 2 values are returned
150                 if (goeResponse.temperatures == null || goeResponse.temperatures.length < 2) {
151                     return UnDefType.UNDEF;
152                 }
153                 return new QuantityType<>(goeResponse.temperatures[0], SIUnits.CELSIUS);
154             case TEMPERATURE_CIRCUIT_BOARD:
155                 if (goeResponse.temperatures == null || goeResponse.temperatures.length < 2) {
156                     return UnDefType.UNDEF;
157                 }
158                 return new QuantityType<>(goeResponse.temperatures[1], SIUnits.CELSIUS);
159             case SESSION_CHARGE_CONSUMPTION:
160                 if (goeResponse.sessionChargeConsumption == null) {
161                     return UnDefType.UNDEF;
162                 }
163                 return new QuantityType<>(goeResponse.sessionChargeConsumption / 1000d, Units.KILOWATT_HOUR);
164             case SESSION_CHARGE_CONSUMPTION_LIMIT:
165                 if (goeResponse.sessionChargeConsumptionLimit == null) {
166                     return UnDefType.UNDEF;
167                 }
168                 return new QuantityType<>(goeResponse.sessionChargeConsumptionLimit / 1000d, Units.KILOWATT_HOUR);
169             case TOTAL_CONSUMPTION:
170                 if (goeResponse.totalChargeConsumption == null) {
171                     return UnDefType.UNDEF;
172                 }
173                 return new QuantityType<>(goeResponse.totalChargeConsumption / 1000d, Units.KILOWATT_HOUR);
174             case VOLTAGE_L1:
175                 if (goeResponse.energy == null || goeResponse.energy.length < 1) {
176                     return UnDefType.UNDEF;
177                 }
178                 return new QuantityType<>(goeResponse.energy[0], Units.VOLT);
179             case VOLTAGE_L2:
180                 if (goeResponse.energy == null || goeResponse.energy.length < 2) {
181                     return UnDefType.UNDEF;
182                 }
183                 return new QuantityType<>(goeResponse.energy[1], Units.VOLT);
184             case VOLTAGE_L3:
185                 if (goeResponse.energy == null || goeResponse.energy.length < 3) {
186                     return UnDefType.UNDEF;
187                 }
188                 return new QuantityType<>(goeResponse.energy[2], Units.VOLT);
189             case CURRENT_L1:
190                 if (goeResponse.energy == null || goeResponse.energy.length < 5) {
191                     return UnDefType.UNDEF;
192                 }
193                 return new QuantityType<>(goeResponse.energy[4], Units.AMPERE);
194             case CURRENT_L2:
195                 if (goeResponse.energy == null || goeResponse.energy.length < 6) {
196                     return UnDefType.UNDEF;
197                 }
198                 return new QuantityType<>(goeResponse.energy[5], Units.AMPERE);
199             case CURRENT_L3:
200                 if (goeResponse.energy == null || goeResponse.energy.length < 7) {
201                     return UnDefType.UNDEF;
202                 }
203                 return new QuantityType<>(goeResponse.energy[6], Units.AMPERE);
204             case POWER_L1:
205                 if (goeResponse.energy == null || goeResponse.energy.length < 8) {
206                     return UnDefType.UNDEF;
207                 }
208                 return new QuantityType<>(goeResponse.energy[7], Units.WATT);
209             case POWER_L2:
210                 if (goeResponse.energy == null || goeResponse.energy.length < 9) {
211                     return UnDefType.UNDEF;
212                 }
213                 return new QuantityType<>(goeResponse.energy[8], Units.WATT);
214             case POWER_L3:
215                 if (goeResponse.energy == null || goeResponse.energy.length < 10) {
216                     return UnDefType.UNDEF;
217                 }
218                 return new QuantityType<>(goeResponse.energy[9], Units.WATT);
219             case POWER_ALL:
220                 if (goeResponse.energy == null || goeResponse.energy.length < 12) {
221                     return UnDefType.UNDEF;
222                 }
223                 return new QuantityType<>(goeResponse.energy[11], Units.WATT);
224             case FORCE_STATE:
225                 if (goeResponse.forceState == null) {
226                     return UnDefType.UNDEF;
227                 }
228                 return new DecimalType(goeResponse.forceState.toString());
229         }
230         return UnDefType.UNDEF;
231     }
232
233     @Override
234     public void handleCommand(ChannelUID channelUID, Command command) {
235         if (command instanceof RefreshType) {
236             // we can not update single channels and refresh is triggered automatically
237             // anyways
238             return;
239         }
240
241         String key = null;
242         String value = null;
243
244         switch (channelUID.getId()) {
245             case MAX_CURRENT:
246                 key = "amp";
247                 if (command instanceof DecimalType decimalCommand) {
248                     value = String.valueOf(decimalCommand.intValue());
249                 } else if (command instanceof QuantityType<?>) {
250                     value = String.valueOf(((QuantityType<ElectricCurrent>) command).toUnit(Units.AMPERE).intValue());
251                 }
252                 break;
253             case SESSION_CHARGE_CONSUMPTION_LIMIT:
254                 key = "dwo";
255                 var multiplier = 1000;
256                 if (command instanceof DecimalType decimalCommand) {
257                     value = String.valueOf(decimalCommand.intValue() * multiplier);
258                 } else if (command instanceof QuantityType<?>) {
259                     value = String.valueOf(
260                             ((QuantityType<Energy>) command).toUnit(Units.KILOWATT_HOUR).intValue() * multiplier);
261                 }
262                 break;
263             case PHASES:
264                 key = "psm";
265                 if (command instanceof DecimalType decimalCommand) {
266                     var phases = 1;
267                     if (decimalCommand.intValue() == 3) {
268                         // set value 2 for 3 phases
269                         phases = 2;
270                     }
271                     value = String.valueOf(phases);
272                 }
273                 break;
274             case FORCE_STATE:
275                 key = "frc";
276                 if (command instanceof DecimalType decimalCommand) {
277                     value = String.valueOf(decimalCommand.intValue());
278                 }
279                 break;
280             case TRANSACTION:
281                 key = "trx";
282                 if (command instanceof DecimalType decimalCommand) {
283                     value = String.valueOf(decimalCommand.intValue());
284                 }
285                 break;
286         }
287
288         if (key != null && value != null) {
289             sendData(key, value);
290         } else {
291             logger.warn("Could not update channel {} with key {} and value {}", channelUID.getId(), key, value);
292         }
293     }
294
295     @Override
296     public void initialize() {
297         // only read needed parameters
298         filter = "?filter=";
299         var declaredFields = GoEStatusResponseV2DTO.class.getDeclaredFields();
300         var declaredFieldsBase = GoEStatusResponseV2DTO.class.getSuperclass().getDeclaredFields();
301
302         for (var field : declaredFields) {
303             filter += field.getAnnotation(SerializedName.class).value() + ",";
304         }
305         for (var field : declaredFieldsBase) {
306             filter += field.getAnnotation(SerializedName.class).value() + ",";
307         }
308         filter = filter.substring(0, filter.length() - 1);
309
310         super.initialize();
311     }
312
313     private String getReadUrl() {
314         return GoEChargerBindingConstants.API_URL_V2.replace("%IP%", config.ip.toString()) + filter;
315     }
316
317     private String getWriteUrl(String key, String value) {
318         return GoEChargerBindingConstants.SET_URL_V2.replace("%IP%", config.ip.toString()).replace("%KEY%", key)
319                 .replace("%VALUE%", value);
320     }
321
322     private void sendData(String key, String value) {
323         String urlStr = getWriteUrl(key, value);
324         logger.trace("POST URL = {}", urlStr);
325
326         try {
327             HttpMethod httpMethod = HttpMethod.GET;
328             ContentResponse contentResponse = httpClient.newRequest(urlStr).method(httpMethod)
329                     .timeout(5, TimeUnit.SECONDS).send();
330             String response = contentResponse.getContentAsString();
331
332             logger.trace("{} Response: {}", httpMethod.toString(), response);
333
334             var statusCode = contentResponse.getStatus();
335             if (!(statusCode == 200 || statusCode == 204)) {
336                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
337                         "@text/unsuccessful.communication-error");
338                 logger.debug("Could not send data, Response {}, StatusCode: {}", response, statusCode);
339             }
340         } catch (InterruptedException ie) {
341             Thread.currentThread().interrupt();
342             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ie.toString());
343             logger.debug("Could not send data: {}, {}", urlStr, ie.toString());
344         } catch (TimeoutException | ExecutionException | JsonSyntaxException e) {
345             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
346             logger.debug("Could not send data: {}, {}", urlStr, e.toString());
347         }
348     }
349
350     /**
351      * Request new data from Go-eCharger
352      *
353      * @return the Go-eCharger object mapping the JSON response or null in case of
354      *         error
355      * @throws ExecutionException
356      * @throws TimeoutException
357      * @throws InterruptedException
358      */
359     @Nullable
360     @Override
361     protected GoEStatusResponseBaseDTO getGoEData()
362             throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
363         String urlStr = getReadUrl();
364         logger.trace("GET URL = {}", urlStr);
365
366         ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
367                 .timeout(5, TimeUnit.SECONDS).send();
368
369         String response = contentResponse.getContentAsString();
370         logger.trace("GET Response: {}", response);
371
372         if (config.apiVersion == 1) {
373             return gson.fromJson(response, GoEStatusResponseDTO.class);
374         }
375         return gson.fromJson(response, GoEStatusResponseV2DTO.class);
376     }
377
378     @Override
379     protected void updateChannelsAndStatus(@Nullable GoEStatusResponseBaseDTO goeResponse, @Nullable String message) {
380         if (goeResponse == null) {
381             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, message);
382             allChannels.forEach(channel -> updateState(channel, UnDefType.UNDEF));
383         } else {
384             updateStatus(ThingStatus.ONLINE);
385             allChannels.forEach(channel -> updateState(channel, getValue(channel, goeResponse)));
386         }
387     }
388 }