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