]> git.basschouten.com Git - openhab-addons.git/blob
826316f5e6ef558dde69403e56630ad1cfb7f878
[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.tesla.internal.handler;
14
15 import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
16
17 import java.io.IOException;
18 import java.math.BigDecimal;
19 import java.math.RoundingMode;
20 import java.net.URI;
21 import java.net.URISyntaxException;
22 import java.text.SimpleDateFormat;
23 import java.util.Arrays;
24 import java.util.Date;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.concurrent.ScheduledFuture;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.locks.ReentrantLock;
32 import java.util.stream.Collectors;
33
34 import javax.measure.quantity.Temperature;
35 import javax.ws.rs.ProcessingException;
36 import javax.ws.rs.client.WebTarget;
37 import javax.ws.rs.core.MediaType;
38 import javax.ws.rs.core.Response;
39
40 import org.eclipse.jdt.annotation.Nullable;
41 import org.openhab.binding.tesla.internal.TeslaBindingConstants;
42 import org.openhab.binding.tesla.internal.TeslaBindingConstants.EventKeys;
43 import org.openhab.binding.tesla.internal.TeslaChannelSelectorProxy;
44 import org.openhab.binding.tesla.internal.TeslaChannelSelectorProxy.TeslaChannelSelector;
45 import org.openhab.binding.tesla.internal.handler.TeslaAccountHandler.Request;
46 import org.openhab.binding.tesla.internal.protocol.ChargeState;
47 import org.openhab.binding.tesla.internal.protocol.ClimateState;
48 import org.openhab.binding.tesla.internal.protocol.DriveState;
49 import org.openhab.binding.tesla.internal.protocol.Event;
50 import org.openhab.binding.tesla.internal.protocol.GUIState;
51 import org.openhab.binding.tesla.internal.protocol.Vehicle;
52 import org.openhab.binding.tesla.internal.protocol.VehicleData;
53 import org.openhab.binding.tesla.internal.protocol.VehicleState;
54 import org.openhab.binding.tesla.internal.throttler.QueueChannelThrottler;
55 import org.openhab.binding.tesla.internal.throttler.Rate;
56 import org.openhab.core.io.net.http.WebSocketFactory;
57 import org.openhab.core.library.types.DecimalType;
58 import org.openhab.core.library.types.IncreaseDecreaseType;
59 import org.openhab.core.library.types.OnOffType;
60 import org.openhab.core.library.types.PercentType;
61 import org.openhab.core.library.types.QuantityType;
62 import org.openhab.core.library.types.StringType;
63 import org.openhab.core.library.unit.SIUnits;
64 import org.openhab.core.library.unit.Units;
65 import org.openhab.core.thing.ChannelUID;
66 import org.openhab.core.thing.Thing;
67 import org.openhab.core.thing.ThingStatus;
68 import org.openhab.core.thing.ThingStatusDetail;
69 import org.openhab.core.thing.binding.BaseThingHandler;
70 import org.openhab.core.types.Command;
71 import org.openhab.core.types.RefreshType;
72 import org.openhab.core.types.State;
73 import org.openhab.core.types.UnDefType;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77 import com.google.gson.Gson;
78 import com.google.gson.JsonElement;
79 import com.google.gson.JsonObject;
80 import com.google.gson.JsonParser;
81
82 /**
83  * The {@link TeslaVehicleHandler} is responsible for handling commands, which are sent
84  * to one of the channels of a specific vehicle.
85  *
86  * @author Karel Goderis - Initial contribution
87  * @author Kai Kreuzer - Refactored to use separate account handler and improved configuration options
88  */
89 public class TeslaVehicleHandler extends BaseThingHandler {
90
91     private static final int FAST_STATUS_REFRESH_INTERVAL = 15000;
92     private static final int SLOW_STATUS_REFRESH_INTERVAL = 60000;
93     private static final int API_SLEEP_INTERVAL_MINUTES = 20;
94     private static final int MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT = 5;
95     private static final int THRESHOLD_INTERVAL_FOR_ADVANCED_MINUTES = 60;
96     private static final int EVENT_MAXIMUM_ERRORS_IN_INTERVAL = 10;
97     private static final int EVENT_ERROR_INTERVAL_SECONDS = 15;
98     private static final int EVENT_STREAM_PAUSE = 3000;
99     private static final int EVENT_TIMESTAMP_AGE_LIMIT = 3000;
100     private static final int EVENT_TIMESTAMP_MAX_DELTA = 10000;
101     private static final int EVENT_PING_INTERVAL = 10000;
102
103     private final Logger logger = LoggerFactory.getLogger(TeslaVehicleHandler.class);
104
105     // Vehicle state variables
106     protected Vehicle vehicle;
107     protected String vehicleJSON;
108     protected DriveState driveState;
109     protected GUIState guiState;
110     protected VehicleState vehicleState;
111     protected ChargeState chargeState;
112     protected ClimateState climateState;
113
114     protected boolean allowWakeUp;
115     protected boolean allowWakeUpForCommands;
116     protected boolean enableEvents = false;
117     protected boolean useDriveState = false;
118     protected boolean useAdvancedStates = false;
119     protected boolean lastValidDriveStateNotNull = true;
120
121     protected long lastTimeStamp;
122     protected long apiIntervalTimestamp;
123     protected int apiIntervalErrors;
124     protected long eventIntervalTimestamp;
125     protected int eventIntervalErrors;
126     protected int inactivity = MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT;
127     protected ReentrantLock lock;
128
129     protected double lastLongitude;
130     protected double lastLatitude;
131     protected long lastLocationChangeTimestamp;
132     protected long lastDriveStateChangeToNullTimestamp;
133     protected long lastAdvModesTimestamp = System.currentTimeMillis();
134     protected long lastStateTimestamp = System.currentTimeMillis();
135     protected int backOffCounter = 0;
136
137     protected String lastState = "";
138     protected boolean isInactive = false;
139
140     protected TeslaAccountHandler account;
141
142     protected QueueChannelThrottler stateThrottler;
143     protected TeslaChannelSelectorProxy teslaChannelSelectorProxy = new TeslaChannelSelectorProxy();
144     protected Thread eventThread;
145     protected ScheduledFuture<?> stateJob;
146     protected WebSocketFactory webSocketFactory;
147
148     private final Gson gson = new Gson();
149
150     public TeslaVehicleHandler(Thing thing, WebSocketFactory webSocketFactory) {
151         super(thing);
152         this.webSocketFactory = webSocketFactory;
153     }
154
155     @SuppressWarnings("null")
156     @Override
157     public void initialize() {
158         logger.trace("Initializing the Tesla handler for {}", getThing().getUID());
159         updateStatus(ThingStatus.UNKNOWN);
160         allowWakeUp = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ALLOWWAKEUP);
161         allowWakeUpForCommands = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ALLOWWAKEUPFORCOMMANDS);
162         enableEvents = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ENABLEEVENTS);
163         Number inactivityParam = (Number) getConfig().get(TeslaBindingConstants.CONFIG_INACTIVITY);
164         inactivity = inactivityParam == null ? MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT : inactivityParam.intValue();
165         Boolean useDriveStateParam = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_USEDRIVESTATE);
166         useDriveState = useDriveStateParam == null ? false : useDriveStateParam;
167         Boolean useAdvancedStatesParam = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_USEDADVANCEDSTATES);
168         useAdvancedStates = useAdvancedStatesParam == null ? false : useAdvancedStatesParam;
169
170         account = (TeslaAccountHandler) getBridge().getHandler();
171         lock = new ReentrantLock();
172         scheduler.execute(this::queryVehicleAndUpdate);
173
174         lock.lock();
175         try {
176             Map<Object, Rate> channels = new HashMap<>();
177             channels.put(DATA_THROTTLE, new Rate(1, 1, TimeUnit.SECONDS));
178             channels.put(COMMAND_THROTTLE, new Rate(20, 1, TimeUnit.MINUTES));
179
180             Rate firstRate = new Rate(20, 1, TimeUnit.MINUTES);
181             Rate secondRate = new Rate(200, 10, TimeUnit.MINUTES);
182             stateThrottler = new QueueChannelThrottler(firstRate, scheduler, channels);
183             stateThrottler.addRate(secondRate);
184
185             if (stateJob == null || stateJob.isCancelled()) {
186                 stateJob = scheduler.scheduleWithFixedDelay(stateRunnable, 0, SLOW_STATUS_REFRESH_INTERVAL,
187                         TimeUnit.MILLISECONDS);
188             }
189
190             if (enableEvents) {
191                 if (eventThread == null) {
192                     eventThread = new Thread(eventRunnable, "OH-binding-" + getThing().getUID() + "-events");
193                     eventThread.start();
194                 }
195             }
196         } finally {
197             lock.unlock();
198         }
199     }
200
201     @Override
202     public void dispose() {
203         logger.trace("Disposing the Tesla handler for {}", getThing().getUID());
204         lock.lock();
205         try {
206             if (stateJob != null && !stateJob.isCancelled()) {
207                 stateJob.cancel(true);
208                 stateJob = null;
209             }
210
211             if (eventThread != null && !eventThread.isInterrupted()) {
212                 eventThread.interrupt();
213                 eventThread = null;
214             }
215         } finally {
216             lock.unlock();
217         }
218     }
219
220     /**
221      * Retrieves the unique vehicle id this handler is associated with
222      *
223      * @return the vehicle id
224      */
225     public String getVehicleId() {
226         if (vehicle != null) {
227             return vehicle.id;
228         } else {
229             return null;
230         }
231     }
232
233     @Override
234     public void handleCommand(ChannelUID channelUID, Command command) {
235         logger.debug("handleCommand {} {}", channelUID, command);
236         String channelID = channelUID.getId();
237         TeslaChannelSelector selector = TeslaChannelSelector.getValueSelectorFromChannelID(channelID);
238
239         if (command instanceof RefreshType) {
240             if (!isAwake()) {
241                 logger.debug("Waking vehicle to refresh all data");
242                 wakeUp();
243             }
244
245             setActive();
246
247             // Request the state of all known variables. This is sub-optimal, but the requests get scheduled and
248             // throttled so we are safe not to break the Tesla SLA
249             requestAllData();
250         } else if (selector != null) {
251             if (!isAwake() && allowWakeUpForCommands) {
252                 logger.debug("Waking vehicle to send command.");
253                 wakeUp();
254                 setActive();
255             }
256             try {
257                 switch (selector) {
258                     case CHARGE_LIMIT_SOC: {
259                         if (command instanceof PercentType percentCommand) {
260                             setChargeLimit(percentCommand.intValue());
261                         } else if (command instanceof OnOffType && command == OnOffType.ON) {
262                             setChargeLimit(100);
263                         } else if (command instanceof OnOffType && command == OnOffType.OFF) {
264                             setChargeLimit(0);
265                         } else if (command instanceof IncreaseDecreaseType
266                                 && command == IncreaseDecreaseType.INCREASE) {
267                             setChargeLimit(Math.min(chargeState.charge_limit_soc + 1, 100));
268                         } else if (command instanceof IncreaseDecreaseType
269                                 && command == IncreaseDecreaseType.DECREASE) {
270                             setChargeLimit(Math.max(chargeState.charge_limit_soc - 1, 0));
271                         }
272                         break;
273                     }
274                     case CHARGE_AMPS:
275                         Integer amps = null;
276                         if (command instanceof DecimalType decimalCommand) {
277                             amps = decimalCommand.intValue();
278                         }
279                         if (command instanceof QuantityType<?> quantityCommand) {
280                             QuantityType<?> qamps = quantityCommand.toUnit(Units.AMPERE);
281                             if (qamps != null) {
282                                 amps = qamps.intValue();
283                             }
284                         }
285                         if (amps != null) {
286                             if (amps < 5 || amps > 32) {
287                                 logger.warn("Charging amps can only be set in a range of 5-32A, but not to {}A.", amps);
288                                 return;
289                             }
290                             setChargingAmps(amps);
291                         }
292                         break;
293                     case COMBINED_TEMP: {
294                         QuantityType<Temperature> quantity = commandToQuantityType(command);
295                         if (quantity != null) {
296                             setCombinedTemperature(quanityToRoundedFloat(quantity));
297                         }
298                         break;
299                     }
300                     case DRIVER_TEMP: {
301                         QuantityType<Temperature> quantity = commandToQuantityType(command);
302                         if (quantity != null) {
303                             setDriverTemperature(quanityToRoundedFloat(quantity));
304                         }
305                         break;
306                     }
307                     case PASSENGER_TEMP: {
308                         QuantityType<Temperature> quantity = commandToQuantityType(command);
309                         if (quantity != null) {
310                             setPassengerTemperature(quanityToRoundedFloat(quantity));
311                         }
312                         break;
313                     }
314                     case SENTRY_MODE: {
315                         if (command instanceof OnOffType) {
316                             setSentryMode(command == OnOffType.ON);
317                         }
318                         break;
319                     }
320                     case SUN_ROOF_STATE: {
321                         if (command instanceof StringType) {
322                             setSunroof(command.toString());
323                         }
324                         break;
325                     }
326                     case CHARGE_TO_MAX: {
327                         if (command instanceof OnOffType onOffCommand) {
328                             if (onOffCommand == OnOffType.ON) {
329                                 setMaxRangeCharging(true);
330                             } else {
331                                 setMaxRangeCharging(false);
332                             }
333                         }
334                         break;
335                     }
336                     case CHARGE: {
337                         if (command instanceof OnOffType onOffCommand) {
338                             if (onOffCommand == OnOffType.ON) {
339                                 charge(true);
340                             } else {
341                                 charge(false);
342                             }
343                         }
344                         break;
345                     }
346                     case FLASH: {
347                         if (command instanceof OnOffType onOffCommand) {
348                             if (onOffCommand == OnOffType.ON) {
349                                 flashLights();
350                             }
351                         }
352                         break;
353                     }
354                     case HONK_HORN: {
355                         if (command instanceof OnOffType onOffCommand) {
356                             if (onOffCommand == OnOffType.ON) {
357                                 honkHorn();
358                             }
359                         }
360                         break;
361                     }
362                     case CHARGEPORT: {
363                         if (command instanceof OnOffType onOffCommand) {
364                             if (onOffCommand == OnOffType.ON) {
365                                 openChargePort();
366                             }
367                         }
368                         break;
369                     }
370                     case DOOR_LOCK: {
371                         if (command instanceof OnOffType onOffCommand) {
372                             if (onOffCommand == OnOffType.ON) {
373                                 lockDoors(true);
374                             } else {
375                                 lockDoors(false);
376                             }
377                         }
378                         break;
379                     }
380                     case AUTO_COND: {
381                         if (command instanceof OnOffType onOffCommand) {
382                             if (onOffCommand == OnOffType.ON) {
383                                 autoConditioning(true);
384                             } else {
385                                 autoConditioning(false);
386                             }
387                         }
388                         break;
389                     }
390                     case WAKEUP: {
391                         if (command instanceof OnOffType onOffCommand) {
392                             if (onOffCommand == OnOffType.ON) {
393                                 wakeUp();
394                             }
395                         }
396                         break;
397                     }
398                     case FT: {
399                         if (command instanceof OnOffType onOffCommand) {
400                             if (onOffCommand == OnOffType.ON) {
401                                 openFrunk();
402                             }
403                         }
404                         break;
405                     }
406                     case RT: {
407                         if (command instanceof OnOffType onOffCommand) {
408                             if (onOffCommand == OnOffType.ON) {
409                                 if (vehicleState.rt == 0) {
410                                     openTrunk();
411                                 }
412                             } else if (vehicleState.rt == 1) {
413                                 closeTrunk();
414                             }
415                         }
416                         break;
417                     }
418                     case VALET_MODE: {
419                         if (command instanceof OnOffType onOffCommand) {
420                             int valetpin = ((BigDecimal) getConfig().get(VALETPIN)).intValue();
421                             if (onOffCommand == OnOffType.ON) {
422                                 setValetMode(true, valetpin);
423                             } else {
424                                 setValetMode(false, valetpin);
425                             }
426                         }
427                         break;
428                     }
429                     case RESET_VALET_PIN: {
430                         if (command instanceof OnOffType onOffCommand) {
431                             if (onOffCommand == OnOffType.ON) {
432                                 resetValetPin();
433                             }
434                         }
435                         break;
436                     }
437                     case STEERINGWHEEL_HEATER: {
438                         if (command instanceof OnOffType onOffCommand) {
439                             boolean commandBooleanValue = onOffCommand == OnOffType.ON ? true : false;
440                             setSteeringWheelHeater(commandBooleanValue);
441                         }
442                         break;
443                     }
444                     default:
445                         break;
446                 }
447                 return;
448             } catch (IllegalArgumentException e) {
449                 logger.warn(
450                         "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'",
451                         channelID, command.toString());
452             }
453         }
454     }
455
456     public void sendCommand(String command, String payLoad, WebTarget target) {
457         if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) {
458             Request request = account.newRequest(this, command, payLoad, target, allowWakeUpForCommands);
459             if (stateThrottler != null) {
460                 stateThrottler.submit(COMMAND_THROTTLE, request);
461             }
462         }
463     }
464
465     public void sendCommand(String command) {
466         sendCommand(command, "{}");
467     }
468
469     public void sendCommand(String command, String payLoad) {
470         if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) {
471             Request request = account.newRequest(this, command, payLoad, account.commandTarget, allowWakeUpForCommands);
472             if (stateThrottler != null) {
473                 stateThrottler.submit(COMMAND_THROTTLE, request);
474             }
475         }
476     }
477
478     public void sendCommand(String command, WebTarget target) {
479         if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) {
480             Request request = account.newRequest(this, command, "{}", target, allowWakeUpForCommands);
481             if (stateThrottler != null) {
482                 stateThrottler.submit(COMMAND_THROTTLE, request);
483             }
484         }
485     }
486
487     public void requestData(String command, String payLoad) {
488         if (COMMAND_WAKE_UP.equals(command) || isAwake()
489                 || (!"vehicleData".equals(command) && allowWakeUpForCommands)) {
490             Request request = account.newRequest(this, command, payLoad, account.dataRequestTarget, false);
491             if (stateThrottler != null) {
492                 stateThrottler.submit(DATA_THROTTLE, request);
493             }
494         }
495     }
496
497     @Override
498     protected void updateStatus(ThingStatus status) {
499         super.updateStatus(status);
500     }
501
502     @Override
503     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail) {
504         super.updateStatus(status, statusDetail);
505     }
506
507     @Override
508     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
509         super.updateStatus(status, statusDetail, description);
510     }
511
512     public void requestData(String command) {
513         requestData(command, null);
514     }
515
516     public void queryVehicle(String parameter) {
517         WebTarget target = account.vehicleTarget.path(parameter);
518         sendCommand(parameter, null, target);
519     }
520
521     public void requestAllData() {
522         requestData("vehicleData", null);
523     }
524
525     protected boolean isAwake() {
526         return vehicle != null && "online".equals(vehicle.state) && vehicle.vehicle_id != null;
527     }
528
529     protected boolean isInMotion() {
530         if (driveState != null) {
531             if (driveState.speed != null && driveState.shift_state != null) {
532                 return !"Undefined".equals(driveState.speed)
533                         && (!"P".equals(driveState.shift_state) || !"Undefined".equals(driveState.shift_state));
534             }
535         }
536         return false;
537     }
538
539     protected boolean isInactive() {
540         // vehicle is inactive in case
541         // - it does not charge
542         // - it has not moved or optionally stopped reporting drive state, in the observation period
543         // - it is not in dog, camp, keep, sentry or any other mode that keeps it online
544         return isInactive && !isCharging() && !notReadyForSleep();
545     }
546
547     protected boolean isCharging() {
548         return chargeState != null && "Charging".equals(chargeState.charging_state);
549     }
550
551     protected boolean notReadyForSleep() {
552         boolean status;
553         int computedInactivityPeriod = inactivity;
554
555         if (useAdvancedStates) {
556             if (vehicleState.is_user_present && !isInMotion()) {
557                 logger.debug("Car is occupied but stationary.");
558                 if (lastAdvModesTimestamp < (System.currentTimeMillis()
559                         - (THRESHOLD_INTERVAL_FOR_ADVANCED_MINUTES * 60 * 1000))) {
560                     logger.debug("Ignoring after {} minutes.", THRESHOLD_INTERVAL_FOR_ADVANCED_MINUTES);
561                 } else {
562                     return (backOffCounter++ % 6 == 0); // using 6 should make sure 1 out of 5 pollers get serviced,
563                                                         // about every min.
564                 }
565             } else if (vehicleState.sentry_mode) {
566                 logger.debug("Car is in sentry mode.");
567                 if (lastAdvModesTimestamp < (System.currentTimeMillis()
568                         - (THRESHOLD_INTERVAL_FOR_ADVANCED_MINUTES * 60 * 1000))) {
569                     logger.debug("Ignoring after {} minutes.", THRESHOLD_INTERVAL_FOR_ADVANCED_MINUTES);
570                 } else {
571                     return (backOffCounter++ % 6 == 0);
572                 }
573             } else if ((vehicleState.center_display_state != 0) && (!isInMotion())) {
574                 logger.debug("Car is in camp, climate keep, dog, or other mode preventing sleep. Mode {}",
575                         vehicleState.center_display_state);
576                 return (backOffCounter++ % 6 == 0);
577             } else {
578                 lastAdvModesTimestamp = System.currentTimeMillis();
579             }
580         }
581
582         if (vehicleState != null && vehicleState.homelink_nearby) {
583             computedInactivityPeriod = MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT;
584             logger.debug("Car is at home. Movement or drive state threshold is {} min.",
585                     MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT);
586         }
587
588         if (useDriveState) {
589             if (driveState.shift_state != null) {
590                 logger.debug("Car drive state not null and not ready to sleep.");
591                 return true;
592             } else {
593                 status = lastDriveStateChangeToNullTimestamp > (System.currentTimeMillis()
594                         - (computedInactivityPeriod * 60 * 1000));
595                 if (status) {
596                     logger.debug("Drivestate is null but has changed recently, therefore continuing to poll.");
597                     return status;
598                 } else {
599                     logger.debug("Drivestate has changed to null after interval {} min and can now be put to sleep.",
600                             computedInactivityPeriod);
601                     return status;
602                 }
603             }
604         } else {
605             status = lastLocationChangeTimestamp > (System.currentTimeMillis()
606                     - (computedInactivityPeriod * 60 * 1000));
607             if (status) {
608                 logger.debug("Car has moved recently and can not sleep");
609                 return status;
610             } else {
611                 logger.debug("Car has not moved in {} min, and can sleep", computedInactivityPeriod);
612                 return status;
613             }
614         }
615     }
616
617     protected boolean allowQuery() {
618         return (isAwake() && !isInactive());
619     }
620
621     protected void setActive() {
622         isInactive = false;
623         lastLocationChangeTimestamp = System.currentTimeMillis();
624         lastDriveStateChangeToNullTimestamp = System.currentTimeMillis();
625         lastLatitude = 0;
626         lastLongitude = 0;
627     }
628
629     protected boolean checkResponse(Response response, boolean immediatelyFail) {
630         if (response != null && response.getStatus() == 200) {
631             return true;
632         } else if (response != null && response.getStatus() == 401) {
633             logger.debug("The access token has expired, trying to get a new one.");
634             account.authenticate();
635         } else {
636             apiIntervalErrors++;
637             if (immediatelyFail || apiIntervalErrors >= TeslaAccountHandler.API_MAXIMUM_ERRORS_IN_INTERVAL) {
638                 if (immediatelyFail) {
639                     logger.warn("Got an unsuccessful result, setting vehicle to offline and will try again");
640                 } else {
641                     logger.warn("Reached the maximum number of errors ({}) for the current interval ({} seconds)",
642                             TeslaAccountHandler.API_MAXIMUM_ERRORS_IN_INTERVAL,
643                             TeslaAccountHandler.API_ERROR_INTERVAL_SECONDS);
644                 }
645
646                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
647             } else if ((System.currentTimeMillis() - apiIntervalTimestamp) > 1000
648                     * TeslaAccountHandler.API_ERROR_INTERVAL_SECONDS) {
649                 logger.trace("Resetting the error counter. ({} errors in the last interval)", apiIntervalErrors);
650                 apiIntervalTimestamp = System.currentTimeMillis();
651                 apiIntervalErrors = 0;
652             }
653         }
654
655         return false;
656     }
657
658     public void setChargeLimit(int percent) {
659         JsonObject payloadObject = new JsonObject();
660         payloadObject.addProperty("percent", percent);
661         sendCommand(COMMAND_SET_CHARGE_LIMIT, gson.toJson(payloadObject), account.commandTarget);
662     }
663
664     public void setChargingAmps(int amps) {
665         JsonObject payloadObject = new JsonObject();
666         payloadObject.addProperty("charging_amps", amps);
667         sendCommand(COMMAND_SET_CHARGING_AMPS, gson.toJson(payloadObject), account.commandTarget);
668     }
669
670     public void setSentryMode(boolean b) {
671         JsonObject payloadObject = new JsonObject();
672         payloadObject.addProperty("on", b);
673         sendCommand(COMMAND_SET_SENTRY_MODE, gson.toJson(payloadObject), account.commandTarget);
674     }
675
676     public void setSunroof(String state) {
677         if ("vent".equals(state) || "close".equals(state)) {
678             JsonObject payloadObject = new JsonObject();
679             payloadObject.addProperty("state", state);
680             sendCommand(COMMAND_SUN_ROOF, gson.toJson(payloadObject), account.commandTarget);
681         } else {
682             logger.warn("Ignoring invalid command '{}' for sunroof.", state);
683         }
684     }
685
686     /**
687      * Sets the driver and passenger temperatures.
688      *
689      * While setting different temperature values is supported by the API, in practice this does not always work
690      * reliably, possibly if the the
691      * only reliable method is to set the driver and passenger temperature to the same value
692      *
693      * @param driverTemperature in Celsius
694      * @param passenegerTemperature in Celsius
695      */
696     public void setTemperature(float driverTemperature, float passenegerTemperature) {
697         JsonObject payloadObject = new JsonObject();
698         payloadObject.addProperty("driver_temp", driverTemperature);
699         payloadObject.addProperty("passenger_temp", passenegerTemperature);
700         sendCommand(COMMAND_SET_TEMP, gson.toJson(payloadObject), account.commandTarget);
701     }
702
703     public void setCombinedTemperature(float temperature) {
704         setTemperature(temperature, temperature);
705     }
706
707     public void setDriverTemperature(float temperature) {
708         setTemperature(temperature, climateState != null ? climateState.passenger_temp_setting : temperature);
709     }
710
711     public void setPassengerTemperature(float temperature) {
712         setTemperature(climateState != null ? climateState.driver_temp_setting : temperature, temperature);
713     }
714
715     public void openFrunk() {
716         JsonObject payloadObject = new JsonObject();
717         payloadObject.addProperty("which_trunk", "front");
718         sendCommand(COMMAND_ACTUATE_TRUNK, gson.toJson(payloadObject), account.commandTarget);
719     }
720
721     public void openTrunk() {
722         JsonObject payloadObject = new JsonObject();
723         payloadObject.addProperty("which_trunk", "rear");
724         sendCommand(COMMAND_ACTUATE_TRUNK, gson.toJson(payloadObject), account.commandTarget);
725     }
726
727     public void closeTrunk() {
728         openTrunk();
729     }
730
731     public void setValetMode(boolean b, Integer pin) {
732         JsonObject payloadObject = new JsonObject();
733         payloadObject.addProperty("on", b);
734         if (pin != null) {
735             payloadObject.addProperty("password", String.format("%04d", pin));
736         }
737         sendCommand(COMMAND_SET_VALET_MODE, gson.toJson(payloadObject), account.commandTarget);
738     }
739
740     public void resetValetPin() {
741         sendCommand(COMMAND_RESET_VALET_PIN, account.commandTarget);
742     }
743
744     public void setMaxRangeCharging(boolean b) {
745         sendCommand(b ? COMMAND_CHARGE_MAX : COMMAND_CHARGE_STD, account.commandTarget);
746     }
747
748     public void charge(boolean b) {
749         sendCommand(b ? COMMAND_CHARGE_START : COMMAND_CHARGE_STOP, account.commandTarget);
750     }
751
752     public void flashLights() {
753         sendCommand(COMMAND_FLASH_LIGHTS, account.commandTarget);
754     }
755
756     public void honkHorn() {
757         sendCommand(COMMAND_HONK_HORN, account.commandTarget);
758     }
759
760     public void openChargePort() {
761         sendCommand(COMMAND_OPEN_CHARGE_PORT, account.commandTarget);
762     }
763
764     public void lockDoors(boolean b) {
765         sendCommand(b ? COMMAND_DOOR_LOCK : COMMAND_DOOR_UNLOCK, account.commandTarget);
766     }
767
768     public void autoConditioning(boolean b) {
769         sendCommand(b ? COMMAND_AUTO_COND_START : COMMAND_AUTO_COND_STOP, account.commandTarget);
770     }
771
772     public void wakeUp() {
773         sendCommand(COMMAND_WAKE_UP, account.wakeUpTarget);
774     }
775
776     public void setSteeringWheelHeater(boolean isOn) {
777         JsonObject payloadObject = new JsonObject();
778         payloadObject.addProperty("on", isOn);
779         sendCommand(COMMAND_STEERING_WHEEL_HEATER, gson.toJson(payloadObject), account.commandTarget);
780     }
781
782     protected Vehicle queryVehicle() {
783         String authHeader = account.getAuthHeader();
784
785         if (authHeader != null) {
786             try {
787                 // get a list of vehicles
788                 synchronized (account.vehiclesTarget) {
789                     Response response = account.vehiclesTarget.request(MediaType.APPLICATION_JSON_TYPE)
790                             .header("Authorization", authHeader).get();
791
792                     logger.debug("Querying the vehicle, response : {}, {}", response.getStatus(),
793                             response.getStatusInfo().getReasonPhrase());
794
795                     if (!checkResponse(response, true)) {
796                         logger.debug("An error occurred while querying the vehicle");
797                         return null;
798                     }
799
800                     JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
801                     Vehicle[] vehicleArray = gson.fromJson(jsonObject.getAsJsonArray("response"), Vehicle[].class);
802
803                     for (Vehicle vehicle : vehicleArray) {
804                         logger.debug("Querying the vehicle: VIN {}", vehicle.vin);
805                         if (vehicle.vin.equals(getConfig().get(VIN))) {
806                             vehicleJSON = gson.toJson(vehicle);
807                             parseAndUpdate("queryVehicle", null, vehicleJSON);
808                             if (logger.isTraceEnabled()) {
809                                 logger.trace("Vehicle is id {}/vehicle_id {}/tokens {}", vehicle.id, vehicle.vehicle_id,
810                                         vehicle.tokens);
811                             }
812                             return vehicle;
813                         }
814                     }
815
816                 }
817             } catch (ProcessingException e) {
818                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
819             }
820         }
821         return null;
822     }
823
824     protected void queryVehicleAndUpdate() {
825         vehicle = queryVehicle();
826     }
827
828     public void parseAndUpdate(String request, String payLoad, String result) {
829         final double locationThreshold = .0000001;
830
831         try {
832             if (request != null && result != null && !"null".equals(result)) {
833                 updateStatus(ThingStatus.ONLINE);
834                 // first, update state objects
835                 if ("queryVehicle".equals(request)) {
836                     if (vehicle != null) {
837                         logger.debug("Vehicle state is {}", vehicle.state);
838                         updateState(TeslaChannelSelector.STATE.getChannelID(), new StringType(vehicle.state));
839                     } else {
840                         logger.debug("Vehicle state is initializing or unknown");
841                         return;
842                     }
843
844                     if (vehicle != null && "asleep".equals(vehicle.state)) {
845                         logger.debug("Vehicle is asleep.");
846                         return;
847                     }
848
849                     if (vehicle != null && !lastState.equals(vehicle.state)) {
850                         lastState = vehicle.state;
851
852                         // in case vehicle changed to awake, refresh all data
853                         if (isAwake()) {
854                             logger.debug("Vehicle is now awake, updating all data");
855                             lastLocationChangeTimestamp = System.currentTimeMillis();
856                             lastDriveStateChangeToNullTimestamp = System.currentTimeMillis();
857                             requestAllData();
858                         }
859
860                         setActive();
861                     }
862
863                     // reset timestamp if elapsed and set inactive to false
864                     if (isInactive && lastStateTimestamp + (API_SLEEP_INTERVAL_MINUTES * 60 * 1000) < System
865                             .currentTimeMillis()) {
866                         logger.debug("Vehicle did not fall asleep within sleep period, checking again");
867                         setActive();
868                     } else {
869                         boolean wasInactive = isInactive;
870                         isInactive = !isCharging() && !notReadyForSleep();
871
872                         if (!wasInactive && isInactive) {
873                             lastStateTimestamp = System.currentTimeMillis();
874                             logger.debug("Vehicle is inactive");
875                         }
876                     }
877                 } else if ("vehicleData".equals(request)) {
878                     VehicleData vehicleData = gson.fromJson(result, VehicleData.class);
879                     if (vehicleData == null) {
880                         logger.error("Not able to parse response '{}'", result);
881                         return;
882                     }
883
884                     driveState = vehicleData.drive_state;
885                     if (Math.abs(lastLatitude - driveState.latitude) > locationThreshold
886                             || Math.abs(lastLongitude - driveState.longitude) > locationThreshold) {
887                         logger.debug("Vehicle moved, resetting last location timestamp");
888
889                         lastLatitude = driveState.latitude;
890                         lastLongitude = driveState.longitude;
891                         lastLocationChangeTimestamp = System.currentTimeMillis();
892                     }
893                     logger.trace("Drive state: {}", driveState.shift_state);
894
895                     if ((driveState.shift_state == null) && (lastValidDriveStateNotNull)) {
896                         logger.debug("Set NULL shiftstate time");
897                         lastValidDriveStateNotNull = false;
898                         lastDriveStateChangeToNullTimestamp = System.currentTimeMillis();
899                     } else if (driveState.shift_state != null) {
900                         logger.trace("Clear NULL shiftstate time");
901                         lastValidDriveStateNotNull = true;
902                     }
903
904                     guiState = vehicleData.gui_settings;
905
906                     vehicleState = vehicleData.vehicle_state;
907
908                     chargeState = vehicleData.charge_state;
909                     if (isCharging()) {
910                         updateState(CHANNEL_CHARGE, OnOffType.ON);
911                     } else {
912                         updateState(CHANNEL_CHARGE, OnOffType.OFF);
913                     }
914
915                     climateState = vehicleData.climate_state;
916                     BigDecimal avgtemp = roundBigDecimal(new BigDecimal(
917                             (climateState.driver_temp_setting + climateState.passenger_temp_setting) / 2.0f));
918                     updateState(CHANNEL_COMBINED_TEMP, new QuantityType<>(avgtemp, SIUnits.CELSIUS));
919
920                     try {
921                         lock.lock();
922
923                         Set<Map.Entry<String, JsonElement>> entrySet = new HashSet<>();
924
925                         entrySet.addAll(gson.toJsonTree(driveState, DriveState.class).getAsJsonObject().entrySet());
926                         entrySet.addAll(gson.toJsonTree(guiState, GUIState.class).getAsJsonObject().entrySet());
927                         entrySet.addAll(gson.toJsonTree(vehicleState, VehicleState.class).getAsJsonObject().entrySet());
928                         entrySet.addAll(gson.toJsonTree(chargeState, ChargeState.class).getAsJsonObject().entrySet());
929                         entrySet.addAll(gson.toJsonTree(climateState, ClimateState.class).getAsJsonObject().entrySet());
930
931                         for (Map.Entry<String, JsonElement> entry : entrySet) {
932                             try {
933                                 TeslaChannelSelector selector = TeslaChannelSelector
934                                         .getValueSelectorFromRESTID(entry.getKey());
935                                 if (!selector.isProperty()) {
936                                     if (!entry.getValue().isJsonNull()) {
937                                         updateState(selector.getChannelID(), teslaChannelSelectorProxy
938                                                 .getState(entry.getValue().getAsString(), selector, editProperties()));
939                                         if (logger.isTraceEnabled()) {
940                                             logger.trace("The variable/value pair '{}':'{}' is successfully processed",
941                                                     entry.getKey(), entry.getValue());
942                                         }
943                                     } else {
944                                         updateState(selector.getChannelID(), UnDefType.UNDEF);
945                                     }
946                                 } else if (!entry.getValue().isJsonNull()) {
947                                     Map<String, String> properties = editProperties();
948                                     properties.put(selector.getChannelID(), entry.getValue().getAsString());
949                                     updateProperties(properties);
950                                     if (logger.isTraceEnabled()) {
951                                         logger.trace(
952                                                 "The variable/value pair '{}':'{}' is successfully used to set property '{}'",
953                                                 entry.getKey(), entry.getValue(), selector.getChannelID());
954                                     }
955                                 }
956                             } catch (IllegalArgumentException e) {
957                                 logger.trace("The variable/value pair '{}':'{}' is not (yet) supported", entry.getKey(),
958                                         entry.getValue());
959                             } catch (ClassCastException | IllegalStateException e) {
960                                 logger.trace("An exception occurred while converting the JSON data : '{}'",
961                                         e.getMessage(), e);
962                             }
963                         }
964                     } finally {
965                         lock.unlock();
966                     }
967                 }
968             }
969         } catch (Exception p) {
970             logger.error("An exception occurred while parsing data received from the vehicle: '{}'", p.getMessage());
971         }
972     }
973
974     @SuppressWarnings("unchecked")
975     protected QuantityType<Temperature> commandToQuantityType(Command command) {
976         if (command instanceof QuantityType) {
977             return ((QuantityType<Temperature>) command).toUnit(SIUnits.CELSIUS);
978         }
979         return new QuantityType<>(new BigDecimal(command.toString()), SIUnits.CELSIUS);
980     }
981
982     protected float quanityToRoundedFloat(QuantityType<Temperature> quantity) {
983         return roundBigDecimal(quantity.toBigDecimal()).floatValue();
984     }
985
986     protected BigDecimal roundBigDecimal(BigDecimal value) {
987         return value.setScale(1, RoundingMode.HALF_EVEN);
988     }
989
990     protected Runnable stateRunnable = () -> {
991         try {
992             queryVehicleAndUpdate();
993             boolean allowQuery = allowQuery();
994
995             if (allowQuery) {
996                 requestAllData();
997             } else if (allowWakeUp) {
998                 wakeUp();
999             } else if (isAwake()) {
1000                 logger.debug("Throttled state polling to allow sleep, occupied/idle, or in a console mode");
1001             } else {
1002                 lastAdvModesTimestamp = System.currentTimeMillis();
1003             }
1004         } catch (Exception e) {
1005             logger.warn("Exception occurred in stateRunnable", e);
1006         }
1007     };
1008
1009     protected Runnable eventRunnable = new Runnable() {
1010         TeslaEventEndpoint eventEndpoint;
1011         boolean isAuthenticated = false;
1012         long lastPingTimestamp = 0;
1013
1014         @Override
1015         public void run() {
1016             eventEndpoint = new TeslaEventEndpoint(getThing().getUID(), webSocketFactory);
1017             eventEndpoint.addEventHandler(new TeslaEventEndpoint.EventHandler() {
1018                 @Override
1019                 public void handleEvent(Event event) {
1020                     if (event != null) {
1021                         switch (event.msg_type) {
1022                             case "control:hello":
1023                                 logger.debug("Event : Received hello");
1024                                 break;
1025                             case "data:update":
1026                                 logger.debug("Event : Received an update: '{}'", event.value);
1027
1028                                 String[] vals = event.value.split(",");
1029                                 long currentTimeStamp = Long.parseLong(vals[0]);
1030                                 long systemTimeStamp = System.currentTimeMillis();
1031                                 if (logger.isDebugEnabled()) {
1032                                     SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
1033                                     logger.debug("STS {} CTS {} Delta {}",
1034                                             dateFormatter.format(new Date(systemTimeStamp)),
1035                                             dateFormatter.format(new Date(currentTimeStamp)),
1036                                             systemTimeStamp - currentTimeStamp);
1037                                 }
1038                                 if (systemTimeStamp - currentTimeStamp < EVENT_TIMESTAMP_AGE_LIMIT) {
1039                                     if (currentTimeStamp > lastTimeStamp) {
1040                                         lastTimeStamp = Long.parseLong(vals[0]);
1041                                         if (logger.isDebugEnabled()) {
1042                                             SimpleDateFormat dateFormatter = new SimpleDateFormat(
1043                                                     "yyyy-MM-dd'T'HH:mm:ss.SSS");
1044                                             logger.debug("Event : Event stamp is {}",
1045                                                     dateFormatter.format(new Date(lastTimeStamp)));
1046                                         }
1047                                         for (int i = 0; i < EventKeys.values().length; i++) {
1048                                             TeslaChannelSelector selector = TeslaChannelSelector
1049                                                     .getValueSelectorFromRESTID((EventKeys.values()[i]).toString());
1050
1051                                             if (!selector.isProperty()) {
1052                                                 State newState = teslaChannelSelectorProxy.getState(vals[i], selector,
1053                                                         editProperties());
1054                                                 if (newState != null && !"".equals(vals[i])) {
1055                                                     updateState(selector.getChannelID(), newState);
1056                                                 } else {
1057                                                     updateState(selector.getChannelID(), UnDefType.UNDEF);
1058                                                 }
1059                                                 if (logger.isTraceEnabled()) {
1060                                                     logger.trace(
1061                                                             "The variable/value pair '{}':'{}' is successfully processed",
1062                                                             EventKeys.values()[i], vals[i]);
1063                                                 }
1064                                             } else {
1065                                                 Map<String, String> properties = editProperties();
1066                                                 properties.put(selector.getChannelID(),
1067                                                         (selector.getState(vals[i])).toString());
1068                                                 updateProperties(properties);
1069                                                 if (logger.isTraceEnabled()) {
1070                                                     logger.trace(
1071                                                             "The variable/value pair '{}':'{}' is successfully used to set property '{}'",
1072                                                             EventKeys.values()[i], vals[i], selector.getChannelID());
1073                                                 }
1074                                             }
1075                                         }
1076                                     } else if (logger.isDebugEnabled()) {
1077                                         SimpleDateFormat dateFormatter = new SimpleDateFormat(
1078                                                 "yyyy-MM-dd'T'HH:mm:ss.SSS");
1079                                         logger.debug(
1080                                                 "Event : Discarding an event with an out of sync timestamp {} (last is {})",
1081                                                 dateFormatter.format(new Date(currentTimeStamp)),
1082                                                 dateFormatter.format(new Date(lastTimeStamp)));
1083                                     }
1084                                 } else {
1085                                     if (logger.isDebugEnabled()) {
1086                                         SimpleDateFormat dateFormatter = new SimpleDateFormat(
1087                                                 "yyyy-MM-dd'T'HH:mm:ss.SSS");
1088                                         logger.debug(
1089                                                 "Event : Discarding an event that differs {} ms from the system time: {} (system is {})",
1090                                                 systemTimeStamp - currentTimeStamp,
1091                                                 dateFormatter.format(currentTimeStamp),
1092                                                 dateFormatter.format(systemTimeStamp));
1093                                     }
1094                                     if (systemTimeStamp - currentTimeStamp > EVENT_TIMESTAMP_MAX_DELTA) {
1095                                         logger.trace("Event : The event endpoint will be reset");
1096                                         eventEndpoint.closeConnection();
1097                                     }
1098                                 }
1099                                 break;
1100                             case "data:error":
1101                                 logger.debug("Event : Received an error: '{}'/'{}'", event.value, event.error_type);
1102                                 eventEndpoint.closeConnection();
1103                                 break;
1104                         }
1105                     }
1106                 }
1107             });
1108
1109             while (true) {
1110                 try {
1111                     if (getThing().getStatus() == ThingStatus.ONLINE) {
1112                         if (isAwake()) {
1113                             eventEndpoint.connect(new URI(URI_EVENT));
1114
1115                             if (eventEndpoint.isConnected()) {
1116                                 if (!isAuthenticated) {
1117                                     logger.debug("Event : Authenticating vehicle {}", vehicle.vehicle_id);
1118                                     JsonObject payloadObject = new JsonObject();
1119                                     payloadObject.addProperty("msg_type", "data:subscribe_oauth");
1120                                     payloadObject.addProperty("token", account.getAccessToken());
1121                                     payloadObject.addProperty("value", Arrays.asList(EventKeys.values()).stream()
1122                                             .skip(1).map(Enum::toString).collect(Collectors.joining(",")));
1123                                     payloadObject.addProperty("tag", vehicle.vehicle_id);
1124
1125                                     eventEndpoint.sendMessage(gson.toJson(payloadObject));
1126                                     isAuthenticated = true;
1127
1128                                     lastPingTimestamp = System.nanoTime();
1129                                 }
1130
1131                                 if (TimeUnit.MILLISECONDS.convert(System.nanoTime() - lastPingTimestamp,
1132                                         TimeUnit.NANOSECONDS) > EVENT_PING_INTERVAL) {
1133                                     logger.trace("Event : Pinging the Tesla event stream infrastructure");
1134                                     eventEndpoint.ping();
1135                                     lastPingTimestamp = System.nanoTime();
1136                                 }
1137                             }
1138
1139                             if (!eventEndpoint.isConnected()) {
1140                                 isAuthenticated = false;
1141                                 eventIntervalErrors++;
1142                                 if (eventIntervalErrors >= EVENT_MAXIMUM_ERRORS_IN_INTERVAL) {
1143                                     logger.warn(
1144                                             "Event : Reached the maximum number of errors ({}) for the current interval ({} seconds)",
1145                                             EVENT_MAXIMUM_ERRORS_IN_INTERVAL, EVENT_ERROR_INTERVAL_SECONDS);
1146                                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
1147                                     eventEndpoint.closeConnection();
1148                                 }
1149
1150                                 if ((System.currentTimeMillis() - eventIntervalTimestamp) > 1000
1151                                         * EVENT_ERROR_INTERVAL_SECONDS) {
1152                                     logger.trace(
1153                                             "Event : Resetting the error counter. ({} errors in the last interval)",
1154                                             eventIntervalErrors);
1155                                     eventIntervalTimestamp = System.currentTimeMillis();
1156                                     eventIntervalErrors = 0;
1157                                 }
1158                             }
1159                         } else {
1160                             logger.debug("Event : The vehicle is not awake");
1161                             if (vehicle != null) {
1162                                 if (allowWakeUp) {
1163                                     // wake up the vehicle until streaming token <> 0
1164                                     logger.debug("Event : Waking up the vehicle");
1165                                     wakeUp();
1166                                 }
1167                             } else {
1168                                 vehicle = queryVehicle();
1169                             }
1170                         }
1171                     }
1172                 } catch (URISyntaxException | NumberFormatException | IOException e) {
1173                     logger.debug("Event : An exception occurred while processing events: '{}'", e.getMessage());
1174                 }
1175
1176                 try {
1177                     Thread.sleep(EVENT_STREAM_PAUSE);
1178                 } catch (InterruptedException e) {
1179                     logger.debug("Event : An exception occurred while putting the event thread to sleep: '{}'",
1180                             e.getMessage());
1181                 }
1182
1183                 if (Thread.interrupted()) {
1184                     logger.debug("Event : The event thread was interrupted");
1185                     eventEndpoint.close();
1186                     return;
1187                 }
1188             }
1189         }
1190     };
1191 }