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