]> git.basschouten.com Git - openhab-addons.git/blob
fae1d60e9e0be0e058e6ce697667ca30a5f14b87
[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.renault.internal.handler;
14
15 import static org.openhab.binding.renault.internal.RenaultBindingConstants.*;
16 import static org.openhab.core.library.unit.MetricPrefix.KILO;
17 import static org.openhab.core.library.unit.SIUnits.METRE;
18 import static org.openhab.core.library.unit.Units.KILOWATT_HOUR;
19 import static org.openhab.core.library.unit.Units.MINUTE;
20
21 import java.util.concurrent.ExecutionException;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.TimeoutException;
25
26 import javax.measure.quantity.Energy;
27 import javax.measure.quantity.Length;
28 import javax.measure.quantity.Temperature;
29 import javax.measure.quantity.Time;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.eclipse.jetty.client.HttpClient;
34 import org.openhab.binding.renault.internal.RenaultBindingConstants;
35 import org.openhab.binding.renault.internal.RenaultConfiguration;
36 import org.openhab.binding.renault.internal.api.Car;
37 import org.openhab.binding.renault.internal.api.Car.ChargingMode;
38 import org.openhab.binding.renault.internal.api.MyRenaultHttpSession;
39 import org.openhab.binding.renault.internal.api.exceptions.RenaultException;
40 import org.openhab.binding.renault.internal.api.exceptions.RenaultForbiddenException;
41 import org.openhab.binding.renault.internal.api.exceptions.RenaultNotImplementedException;
42 import org.openhab.binding.renault.internal.api.exceptions.RenaultUpdateException;
43 import org.openhab.core.library.types.DateTimeType;
44 import org.openhab.core.library.types.DecimalType;
45 import org.openhab.core.library.types.PointType;
46 import org.openhab.core.library.types.QuantityType;
47 import org.openhab.core.library.types.StringType;
48 import org.openhab.core.library.unit.SIUnits;
49 import org.openhab.core.thing.ChannelUID;
50 import org.openhab.core.thing.Thing;
51 import org.openhab.core.thing.ThingStatus;
52 import org.openhab.core.thing.ThingStatusDetail;
53 import org.openhab.core.thing.binding.BaseThingHandler;
54 import org.openhab.core.types.Command;
55 import org.openhab.core.types.RefreshType;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 /**
60  * The {@link RenaultHandler} is responsible for handling commands, which are
61  * sent to one of the channels.
62  *
63  * @author Doug Culnane - Initial contribution
64  */
65 @NonNullByDefault
66 public class RenaultHandler extends BaseThingHandler {
67
68     private final Logger logger = LoggerFactory.getLogger(RenaultHandler.class);
69
70     private RenaultConfiguration config = new RenaultConfiguration();
71
72     private @Nullable ScheduledFuture<?> pollingJob;
73
74     private HttpClient httpClient;
75
76     private Car car;
77
78     public RenaultHandler(Thing thing, HttpClient httpClient) {
79         super(thing);
80         this.car = new Car();
81         this.httpClient = httpClient;
82     }
83
84     @Override
85     public void initialize() {
86         // reset the car on initialize
87         this.car = new Car();
88         this.config = getConfigAs(RenaultConfiguration.class);
89
90         // Validate configuration
91         if (this.config.myRenaultUsername.isBlank()) {
92             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "MyRenault Username is empty!");
93             return;
94         }
95         if (this.config.myRenaultPassword.isBlank()) {
96             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "MyRenault Password is empty!");
97             return;
98         }
99         if (this.config.locale.isBlank()) {
100             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Location is empty!");
101             return;
102         }
103         if (this.config.vin.isBlank()) {
104             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "VIN is empty!");
105             return;
106         }
107         if (this.config.refreshInterval < 1) {
108             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
109                     "The refresh interval mush to be larger than 1");
110             return;
111         }
112         updateStatus(ThingStatus.UNKNOWN);
113
114         updateState(CHANNEL_HVAC_TARGET_TEMPERATURE,
115                 new QuantityType<Temperature>(car.getHvacTargetTemperature(), SIUnits.CELSIUS));
116
117         // Background initialization:
118         ScheduledFuture<?> job = pollingJob;
119         if (job == null || job.isCancelled()) {
120             pollingJob = scheduler.scheduleWithFixedDelay(this::getStatus, 0, config.refreshInterval, TimeUnit.MINUTES);
121         }
122     }
123
124     @Override
125     public void handleCommand(ChannelUID channelUID, Command command) {
126         switch (channelUID.getId()) {
127             case RenaultBindingConstants.CHANNEL_HVAC_TARGET_TEMPERATURE:
128                 if (!car.isDisableHvac()) {
129                     if (command instanceof RefreshType) {
130                         updateState(CHANNEL_HVAC_TARGET_TEMPERATURE,
131                                 new QuantityType<Temperature>(car.getHvacTargetTemperature(), SIUnits.CELSIUS));
132                     } else if (command instanceof DecimalType) {
133                         car.setHvacTargetTemperature(((DecimalType) command).doubleValue());
134                         updateState(CHANNEL_HVAC_TARGET_TEMPERATURE,
135                                 new QuantityType<Temperature>(car.getHvacTargetTemperature(), SIUnits.CELSIUS));
136                     } else if (command instanceof QuantityType) {
137                         @Nullable
138                         QuantityType<Temperature> celsius = ((QuantityType<Temperature>) command)
139                                 .toUnit(SIUnits.CELSIUS);
140                         if (celsius != null) {
141                             car.setHvacTargetTemperature(celsius.doubleValue());
142                         }
143                         updateState(CHANNEL_HVAC_TARGET_TEMPERATURE,
144                                 new QuantityType<Temperature>(car.getHvacTargetTemperature(), SIUnits.CELSIUS));
145                     }
146                 }
147                 break;
148             case RenaultBindingConstants.CHANNEL_HVAC_STATUS:
149                 // We can only trigger pre-conditioning of the car.
150                 if (command instanceof StringType && command.toString().equals(Car.HVAC_STATUS_ON)
151                         && !car.isDisableHvac()) {
152                     final MyRenaultHttpSession httpSession = new MyRenaultHttpSession(this.config, httpClient);
153                     try {
154                         updateState(CHANNEL_HVAC_STATUS, new StringType(Car.HVAC_STATUS_PENDING));
155                         car.resetHVACStatus();
156                         httpSession.initSesssion(car);
157                         httpSession.actionHvacOn(car.getHvacTargetTemperature());
158                         if (pollingJob != null) {
159                             pollingJob.cancel(true);
160                         }
161                         pollingJob = scheduler.scheduleWithFixedDelay(this::getStatus, config.updateDelay,
162                                 config.refreshInterval * 60, TimeUnit.SECONDS);
163                     } catch (InterruptedException e) {
164                         logger.warn("Error My Renault Http Session.", e);
165                         Thread.currentThread().interrupt();
166                     } catch (RenaultException | RenaultForbiddenException | RenaultUpdateException
167                             | RenaultNotImplementedException | ExecutionException | TimeoutException e) {
168                         logger.warn("Error My Renault Http Session.", e);
169                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
170                     }
171                 }
172                 break;
173             case RenaultBindingConstants.CHANNEL_CHARGING_MODE:
174                 if (command instanceof StringType) {
175                     try {
176                         ChargingMode newMode = ChargingMode.valueOf(command.toString());
177                         if (!ChargingMode.UNKNOWN.equals(newMode)) {
178                             MyRenaultHttpSession httpSession = new MyRenaultHttpSession(this.config, httpClient);
179                             try {
180                                 httpSession.initSesssion(car);
181                                 httpSession.actionChargeMode(newMode);
182                                 car.setChargeMode(newMode);
183                                 updateState(CHANNEL_CHARGING_MODE, new StringType(newMode.toString()));
184                             } catch (InterruptedException e) {
185                                 logger.warn("Error My Renault Http Session.", e);
186                                 Thread.currentThread().interrupt();
187                             } catch (RenaultException | RenaultForbiddenException | RenaultUpdateException
188                                     | RenaultNotImplementedException | ExecutionException | TimeoutException e) {
189                                 logger.warn("Error My Renault Http Session.", e);
190                                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
191                                         e.getMessage());
192                             }
193                         }
194                     } catch (IllegalArgumentException e) {
195                         logger.warn("Invalid ChargingMode {}.", command.toString());
196                         return;
197                     }
198                 }
199             default:
200                 break;
201         }
202     }
203
204     @Override
205     public void dispose() {
206         ScheduledFuture<?> job = pollingJob;
207         if (job != null) {
208             job.cancel(true);
209             pollingJob = null;
210         }
211         super.dispose();
212     }
213
214     private void getStatus() {
215         MyRenaultHttpSession httpSession = new MyRenaultHttpSession(this.config, httpClient);
216         try {
217             httpSession.initSesssion(car);
218             updateStatus(ThingStatus.ONLINE);
219         } catch (InterruptedException e) {
220             logger.warn("Error My Renault Http Session.", e);
221             Thread.currentThread().interrupt();
222         } catch (Exception e) {
223             logger.warn("Error My Renault Http Session.", e);
224             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
225         }
226         if (httpSession != null) {
227             String imageURL = car.getImageURL();
228             if (imageURL != null && !imageURL.isEmpty()) {
229                 updateState(CHANNEL_IMAGE, new StringType(imageURL));
230             }
231             updateHvacStatus(httpSession);
232             updateCockpit(httpSession);
233             updateLocation(httpSession);
234             updateBattery(httpSession);
235         }
236     }
237
238     private void updateHvacStatus(MyRenaultHttpSession httpSession) {
239         if (!car.isDisableHvac()) {
240             try {
241                 httpSession.getHvacStatus(car);
242                 Boolean hvacstatus = car.getHvacstatus();
243                 if (hvacstatus == null) {
244                     updateState(CHANNEL_HVAC_STATUS, new StringType(Car.HVAC_STATUS_PENDING));
245                 } else if (hvacstatus.booleanValue()) {
246                     updateState(CHANNEL_HVAC_STATUS, new StringType(Car.HVAC_STATUS_ON));
247                 } else {
248                     updateState(CHANNEL_HVAC_STATUS, new StringType(Car.HVAC_STATUS_OFF));
249                 }
250                 Double externalTemperature = car.getExternalTemperature();
251                 if (externalTemperature != null) {
252                     updateState(CHANNEL_EXTERNAL_TEMPERATURE,
253                             new QuantityType<Temperature>(externalTemperature.doubleValue(), SIUnits.CELSIUS));
254                 }
255             } catch (RenaultNotImplementedException e) {
256                 car.setDisableHvac(true);
257             } catch (RenaultForbiddenException | RenaultUpdateException e) {
258             }
259         }
260     }
261
262     private void updateLocation(MyRenaultHttpSession httpSession) {
263         if (!car.isDisableLocation()) {
264             try {
265                 httpSession.getLocation(car);
266                 Double latitude = car.getGpsLatitude();
267                 Double longitude = car.getGpsLongitude();
268                 if (latitude != null && longitude != null) {
269                     updateState(CHANNEL_LOCATION, new PointType(new DecimalType(latitude.doubleValue()),
270                             new DecimalType(longitude.doubleValue())));
271                 }
272                 String locationUpdated = car.getLocationUpdated();
273                 if (locationUpdated != null) {
274                     updateState(CHANNEL_LOCATION_UPDATED, new DateTimeType(locationUpdated));
275                 }
276             } catch (RenaultNotImplementedException e) {
277                 car.setDisableLocation(true);
278             } catch (IllegalArgumentException | RenaultForbiddenException | RenaultUpdateException e) {
279             }
280         }
281     }
282
283     private void updateCockpit(MyRenaultHttpSession httpSession) {
284         if (!car.isDisableCockpit()) {
285             try {
286                 httpSession.getCockpit(car);
287                 Double odometer = car.getOdometer();
288                 if (odometer != null) {
289                     updateState(CHANNEL_ODOMETER, new QuantityType<Length>(odometer.doubleValue(), KILO(METRE)));
290                 }
291             } catch (RenaultNotImplementedException e) {
292                 car.setDisableCockpit(true);
293             } catch (RenaultForbiddenException | RenaultUpdateException e) {
294             }
295         }
296     }
297
298     private void updateBattery(MyRenaultHttpSession httpSession) {
299         if (!car.isDisableBattery()) {
300             try {
301                 httpSession.getBatteryStatus(car);
302                 updateState(CHANNEL_PLUG_STATUS, new StringType(car.getPlugStatus().name()));
303                 updateState(CHANNEL_CHARGING_STATUS, new StringType(car.getChargingStatus().name()));
304                 Double batteryLevel = car.getBatteryLevel();
305                 if (batteryLevel != null) {
306                     updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue()));
307                 }
308                 Double estimatedRange = car.getEstimatedRange();
309                 if (estimatedRange != null) {
310                     updateState(CHANNEL_ESTIMATED_RANGE,
311                             new QuantityType<Length>(estimatedRange.doubleValue(), KILO(METRE)));
312                 }
313                 Double batteryAvailableEnergy = car.getBatteryAvailableEnergy();
314                 if (batteryAvailableEnergy != null) {
315                     updateState(CHANNEL_BATTERY_AVAILABLE_ENERGY,
316                             new QuantityType<Energy>(batteryAvailableEnergy.doubleValue(), KILOWATT_HOUR));
317                 }
318                 Integer chargingRemainingTime = car.getChargingRemainingTime();
319                 if (chargingRemainingTime != null) {
320                     updateState(CHANNEL_CHARGING_REMAINING_TIME,
321                             new QuantityType<Time>(chargingRemainingTime.doubleValue(), MINUTE));
322                 }
323             } catch (RenaultNotImplementedException e) {
324                 car.setDisableBattery(true);
325             } catch (RenaultForbiddenException | RenaultUpdateException e) {
326             }
327         }
328     }
329 }