]> git.basschouten.com Git - openhab-addons.git/blob
80539e26d5cfcbc414f8ad945de4b15572f053c9
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.vizio.internal.handler;
14
15 import static org.openhab.binding.vizio.internal.VizioBindingConstants.*;
16
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Locale;
21 import java.util.Optional;
22 import java.util.Random;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.eclipse.jetty.util.ssl.SslContextFactory;
30 import org.openhab.binding.vizio.internal.VizioConfiguration;
31 import org.openhab.binding.vizio.internal.VizioException;
32 import org.openhab.binding.vizio.internal.VizioStateDescriptionOptionProvider;
33 import org.openhab.binding.vizio.internal.communication.VizioCommunicator;
34 import org.openhab.binding.vizio.internal.dto.app.CurrentApp;
35 import org.openhab.binding.vizio.internal.dto.applist.VizioApp;
36 import org.openhab.binding.vizio.internal.dto.applist.VizioApps;
37 import org.openhab.binding.vizio.internal.dto.audio.Audio;
38 import org.openhab.binding.vizio.internal.dto.audio.ItemAudio;
39 import org.openhab.binding.vizio.internal.dto.input.CurrentInput;
40 import org.openhab.binding.vizio.internal.dto.inputlist.InputList;
41 import org.openhab.binding.vizio.internal.dto.power.PowerMode;
42 import org.openhab.binding.vizio.internal.enums.KeyCommand;
43 import org.openhab.core.config.core.Configuration;
44 import org.openhab.core.io.net.http.HttpClientFactory;
45 import org.openhab.core.library.types.NextPreviousType;
46 import org.openhab.core.library.types.OnOffType;
47 import org.openhab.core.library.types.PercentType;
48 import org.openhab.core.library.types.PlayPauseType;
49 import org.openhab.core.library.types.RewindFastforwardType;
50 import org.openhab.core.library.types.StringType;
51 import org.openhab.core.thing.Channel;
52 import org.openhab.core.thing.ChannelUID;
53 import org.openhab.core.thing.Thing;
54 import org.openhab.core.thing.ThingStatus;
55 import org.openhab.core.thing.ThingStatusDetail;
56 import org.openhab.core.thing.binding.BaseThingHandler;
57 import org.openhab.core.thing.util.ThingWebClientUtil;
58 import org.openhab.core.types.Command;
59 import org.openhab.core.types.RefreshType;
60 import org.openhab.core.types.StateOption;
61 import org.openhab.core.types.UnDefType;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 import com.google.gson.Gson;
66 import com.google.gson.JsonSyntaxException;
67
68 /**
69  * The {@link VizioHandler} is responsible for handling commands, which are
70  * sent to one of the channels.
71  *
72  * @author Michael Lobstein - Initial contribution
73  */
74 @NonNullByDefault
75 public class VizioHandler extends BaseThingHandler {
76     private final Logger logger = LoggerFactory.getLogger(VizioHandler.class);
77     private final HttpClientFactory httpClientFactory;
78     private @Nullable HttpClient httpClient;
79     private final VizioStateDescriptionOptionProvider stateDescriptionProvider;
80     private final String dbAppsJson;
81
82     private @Nullable ScheduledFuture<?> refreshJob;
83     private @Nullable ScheduledFuture<?> metadataRefreshJob;
84
85     private VizioCommunicator communicator;
86     private List<VizioApp> userConfigApps = new ArrayList<>();
87     private Object sequenceLock = new Object();
88
89     private int pairingDeviceId = -1;
90     private int pairingToken = -1;
91     private Long currentInputHash = 0L;
92     private Long currentVolumeHash = 0L;
93     private String currentApp = EMPTY;
94     private String currentInput = EMPTY;
95     private boolean currentMute = false;
96     private int currentVolume = -1;
97     private boolean powerOn = false;
98     private boolean debounce = true;
99
100     public VizioHandler(Thing thing, HttpClientFactory httpClientFactory,
101             VizioStateDescriptionOptionProvider stateDescriptionProvider, String vizioAppsJson) {
102         super(thing);
103         this.httpClientFactory = httpClientFactory;
104         this.stateDescriptionProvider = stateDescriptionProvider;
105         this.dbAppsJson = vizioAppsJson;
106         this.communicator = new VizioCommunicator(httpClientFactory.getCommonHttpClient(), EMPTY, -1, EMPTY);
107     }
108
109     @Override
110     public void initialize() {
111         logger.debug("Initializing Vizio handler");
112         final Gson gson = new Gson();
113         VizioConfiguration config = getConfigAs(VizioConfiguration.class);
114
115         @Nullable
116         String host = config.hostName;
117         final @Nullable String authToken = config.authToken;
118         @Nullable
119         String appListJson = config.appListJson;
120
121         if (host == null || host.isEmpty()) {
122             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
123                     "@text/offline.configuration-error-hostname");
124             return;
125         } else if (host.contains(":")) {
126             // format for ipv6
127             host = "[" + host + "]";
128         }
129
130         final String httpClientName = ThingWebClientUtil.buildWebClientConsumerName(thing.getUID(), null);
131         try {
132             httpClient = httpClientFactory.createHttpClient(httpClientName, new SslContextFactory.Client(true));
133             final HttpClient localHttpClient = this.httpClient;
134             if (localHttpClient != null) {
135                 localHttpClient.start();
136                 this.communicator = new VizioCommunicator(localHttpClient, host, config.port,
137                         authToken != null ? authToken : EMPTY);
138             }
139         } catch (Exception e) {
140             logger.error(
141                     "Long running HttpClient for Vizio handler {} cannot be started. Creating Handler failed. Exception: {}",
142                     httpClientName, e.getMessage(), e);
143             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
144             return;
145         }
146
147         if (authToken == null) {
148             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
149                     "@text/offline.configuration-error-authtoken");
150             return;
151         }
152
153         // if app list is not supplied in thing configuration, populate it from the json db
154         if (appListJson == null) {
155             appListJson = dbAppsJson;
156
157             // Update thing configuration (persistent) - store app list from db into thing so the user can update it
158             Configuration configuration = this.getConfig();
159             configuration.put(PROPERTY_APP_LIST_JSON, appListJson);
160             this.updateConfiguration(configuration);
161         }
162
163         try {
164             VizioApps appsFromJson = gson.fromJson(appListJson, VizioApps.class);
165             if (appsFromJson != null && !appsFromJson.getApps().isEmpty()) {
166                 userConfigApps = appsFromJson.getApps();
167
168                 List<StateOption> appListOptions = new ArrayList<>();
169                 userConfigApps.forEach(app -> {
170                     appListOptions.add(new StateOption(app.getName(), app.getName()));
171                 });
172
173                 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), ACTIVE_APP),
174                         appListOptions);
175             }
176         } catch (JsonSyntaxException e) {
177             logger.debug("Invalid App List Configuration in thing configuration. Exception: {}", e.getMessage(), e);
178             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
179                     "@text/offline.configuration-error-applist");
180             return;
181         }
182
183         updateStatus(ThingStatus.UNKNOWN);
184
185         startVizioStateRefresh();
186         startPeriodicRefresh();
187     }
188
189     /**
190      * Start the job that queries the Vizio TV every 10 seconds to get its current status
191      */
192     private void startVizioStateRefresh() {
193         ScheduledFuture<?> refreshJob = this.refreshJob;
194         if (refreshJob == null || refreshJob.isCancelled()) {
195             this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshVizioState, 5, 10, TimeUnit.SECONDS);
196         }
197     }
198
199     /**
200      * Get current status from the Vizio TV and update the channels
201      */
202     private void refreshVizioState() {
203         synchronized (sequenceLock) {
204             try {
205                 PowerMode polledPowerMode = communicator.getPowerMode();
206
207                 if (debounce && !polledPowerMode.getItems().isEmpty()) {
208                     int powerMode = polledPowerMode.getItems().get(0).getValue();
209                     if (powerMode == 1) {
210                         powerOn = true;
211                         updateState(POWER, OnOffType.ON);
212                     } else if (powerMode == 0) {
213                         powerOn = false;
214                         updateState(POWER, OnOffType.OFF);
215                     } else {
216                         logger.debug("Unknown power mode {}, for response object: {}", powerMode, polledPowerMode);
217                     }
218                 }
219                 updateStatus(ThingStatus.ONLINE);
220             } catch (VizioException e) {
221                 logger.debug("Unable to retrieve Vizio TV power mode info. Exception: {}", e.getMessage(), e);
222                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
223                         "@text/offline.communication-error-get-power");
224             }
225
226             if (powerOn && (isLinked(VOLUME) || isLinked(MUTE))) {
227                 try {
228                     Audio audioSettings = communicator.getCurrentAudioSettings();
229
230                     Optional<ItemAudio> volumeItem = audioSettings.getItems().stream()
231                             .filter(i -> VOLUME.equals(i.getCname())).findFirst();
232                     if (debounce && volumeItem.isPresent()) {
233                         currentVolumeHash = volumeItem.get().getHashval();
234
235                         try {
236                             int polledVolume = Integer.parseInt(volumeItem.get().getValue());
237                             if (polledVolume != currentVolume) {
238                                 currentVolume = polledVolume;
239                                 updateState(VOLUME, new PercentType(BigDecimal.valueOf(currentVolume)));
240                             }
241                         } catch (NumberFormatException e) {
242                             logger.debug("Unable to parse volume value {} as int", volumeItem.get().getValue());
243                         }
244                     }
245
246                     Optional<ItemAudio> muteItem = audioSettings.getItems().stream()
247                             .filter(i -> MUTE.equals(i.getCname())).findFirst();
248                     if (debounce && muteItem.isPresent()) {
249                         String polledMute = muteItem.get().getValue().toUpperCase(Locale.ENGLISH);
250
251                         if (ON.equals(polledMute) || OFF.equals(polledMute)) {
252                             if (ON.equals(polledMute) && !currentMute) {
253                                 updateState(MUTE, OnOffType.ON);
254                                 currentMute = true;
255                             } else if (OFF.equals(polledMute) && currentMute) {
256                                 updateState(MUTE, OnOffType.OFF);
257                                 currentMute = false;
258                             }
259                         } else {
260                             logger.debug("Unknown mute mode {}, for response object: {}", polledMute, audioSettings);
261                         }
262                     }
263                 } catch (VizioException e) {
264                     logger.debug("Unable to retrieve Vizio TV current audio settings. Exception: {}", e.getMessage(),
265                             e);
266                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
267                             "@text/offline.communication-error-get-audio");
268                 }
269             }
270
271             if (powerOn && isLinked(SOURCE)) {
272                 try {
273                     CurrentInput polledInputState = communicator.getCurrentInput();
274
275                     if (debounce && !polledInputState.getItems().isEmpty()
276                             && !currentInput.equals(polledInputState.getItems().get(0).getValue())) {
277                         currentInput = polledInputState.getItems().get(0).getValue();
278                         currentInputHash = polledInputState.getItems().get(0).getHashval();
279                         updateState(SOURCE, new StringType(currentInput));
280                     }
281                 } catch (VizioException e) {
282                     logger.debug("Unable to retrieve Vizio TV current input. Exception: {}", e.getMessage(), e);
283                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
284                             "@text/offline.communication-error-get-input");
285                 }
286             }
287
288             if (powerOn && isLinked(ACTIVE_APP)) {
289                 try {
290                     if (debounce) {
291                         CurrentApp polledApp = communicator.getCurrentApp();
292                         Optional<VizioApp> currentAppData = userConfigApps.stream()
293                                 .filter(a -> a.getConfig().getAppId().equals(polledApp.getItem().getValue().getAppId())
294                                         && a.getConfig().getNameSpace()
295                                                 .equals(polledApp.getItem().getValue().getNameSpace()))
296                                 .findFirst();
297
298                         if (currentAppData.isPresent()) {
299                             if (!currentApp.equals(currentAppData.get().getName())) {
300                                 currentApp = currentAppData.get().getName();
301                                 updateState(ACTIVE_APP, new StringType(currentApp));
302                             }
303                         } else {
304                             currentApp = EMPTY;
305                             try {
306                                 int appId = Integer.parseInt(polledApp.getItem().getValue().getAppId());
307                                 updateState(ACTIVE_APP, new StringType(String.format(UNKNOWN_APP_STR, appId,
308                                         polledApp.getItem().getValue().getNameSpace())));
309                             } catch (NumberFormatException nfe) {
310                                 // Non-numeric appId received, eg: hdmi1
311                                 updateState(ACTIVE_APP, UnDefType.UNDEF);
312                             }
313
314                             logger.debug("Unknown app_id: {}, name_space: {}",
315                                     polledApp.getItem().getValue().getAppId(),
316                                     polledApp.getItem().getValue().getNameSpace());
317                         }
318                     }
319                 } catch (VizioException e) {
320                     logger.debug("Unable to retrieve Vizio TV current running app. Exception: {}", e.getMessage(), e);
321                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
322                             "@text/offline.communication-error-get-app");
323                 }
324             }
325         }
326         debounce = true;
327     }
328
329     /**
330      * Start the job to periodically retrieve various metadata from the Vizio TV every 10 minutes
331      */
332     private void startPeriodicRefresh() {
333         ScheduledFuture<?> metadataRefreshJob = this.metadataRefreshJob;
334         if (metadataRefreshJob == null || metadataRefreshJob.isCancelled()) {
335             this.metadataRefreshJob = scheduler.scheduleWithFixedDelay(this::refreshVizioMetadata, 1, 600,
336                     TimeUnit.SECONDS);
337         }
338     }
339
340     /**
341      * Update source list (hashes) and other metadata from the Vizio TV
342      */
343     private void refreshVizioMetadata() {
344         synchronized (sequenceLock) {
345             try {
346                 InputList inputList = communicator.getSourceInputList();
347
348                 List<StateOption> sourceListOptions = new ArrayList<>();
349                 inputList.getItems().forEach(source -> {
350                     sourceListOptions.add(new StateOption(source.getName(), source.getValue().getName()));
351                 });
352
353                 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), SOURCE),
354                         sourceListOptions);
355             } catch (VizioException e) {
356                 logger.debug("Unable to retrieve the Vizio TV input list. Exception: {}", e.getMessage(), e);
357             }
358         }
359     }
360
361     @Override
362     public void dispose() {
363         ScheduledFuture<?> refreshJob = this.refreshJob;
364         if (refreshJob != null) {
365             refreshJob.cancel(true);
366             this.refreshJob = null;
367         }
368
369         ScheduledFuture<?> metadataRefreshJob = this.metadataRefreshJob;
370         if (metadataRefreshJob != null) {
371             metadataRefreshJob.cancel(true);
372             this.metadataRefreshJob = null;
373         }
374
375         try {
376             HttpClient localHttpClient = this.httpClient;
377             if (localHttpClient != null) {
378                 localHttpClient.stop();
379             }
380             this.httpClient = null;
381         } catch (Exception e) {
382             logger.debug("Unable to stop Vizio httpClient. Exception: {}", e.getMessage(), e);
383         }
384     }
385
386     @Override
387     public void handleCommand(ChannelUID channelUID, Command command) {
388         if (command instanceof RefreshType) {
389             logger.debug("Unsupported refresh command: {}", command);
390         } else {
391             switch (channelUID.getId()) {
392                 case POWER:
393                     debounce = false;
394                     synchronized (sequenceLock) {
395                         try {
396                             if (command == OnOffType.ON) {
397                                 communicator.sendKeyPress(KeyCommand.POWERON.getJson());
398                                 powerOn = true;
399                             } else {
400                                 communicator.sendKeyPress(KeyCommand.POWEROFF.getJson());
401                                 powerOn = false;
402                             }
403                         } catch (VizioException e) {
404                             logger.debug("Unable to send power {} command to the Vizio TV, Exception: {}", command,
405                                     e.getMessage());
406                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
407                                     "@text/offline.communication-error-set-power");
408                         }
409                     }
410                     break;
411                 case VOLUME:
412                     debounce = false;
413                     synchronized (sequenceLock) {
414                         try {
415                             int volume = Integer.parseInt(command.toString());
416
417                             // volume changed again before polling has run, get current volume hash from the TV first
418                             if (currentVolumeHash.equals(0L)) {
419                                 Audio audioSettings = communicator.getCurrentAudioSettings();
420
421                                 Optional<ItemAudio> volumeItem = audioSettings.getItems().stream()
422                                         .filter(i -> VOLUME.equals(i.getCname())).findFirst();
423                                 if (volumeItem.isPresent()) {
424                                     currentVolumeHash = volumeItem.get().getHashval();
425                                 } else {
426                                     logger.debug("Unable to get current volume hash on the Vizio TV");
427                                 }
428                             }
429                             communicator
430                                     .changeVolume(String.format(MODIFY_INT_SETTING_JSON, volume, currentVolumeHash));
431                             currentVolumeHash = 0L;
432                         } catch (VizioException e) {
433                             logger.debug("Unable to set volume on the Vizio TV, command volume: {}, Exception: {}",
434                                     command, e.getMessage());
435                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
436                                     "@text/offline.communication-error-set-volume");
437                         } catch (NumberFormatException e) {
438                             logger.debug("Unable to parse command volume value {} as int", command);
439                         }
440                     }
441                     break;
442                 case MUTE:
443                     debounce = false;
444                     synchronized (sequenceLock) {
445                         try {
446                             if (command == OnOffType.ON && !currentMute) {
447                                 communicator.sendKeyPress(KeyCommand.MUTETOGGLE.getJson());
448                                 currentMute = true;
449                             } else if (command == OnOffType.OFF && currentMute) {
450                                 communicator.sendKeyPress(KeyCommand.MUTETOGGLE.getJson());
451                                 currentMute = false;
452                             }
453                         } catch (VizioException e) {
454                             logger.debug("Unable to send mute {} command to the Vizio TV, Exception: {}", command,
455                                     e.getMessage());
456                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
457                                     "@text/offline.communication-error-set-mute");
458                         }
459                     }
460                     break;
461                 case SOURCE:
462                     debounce = false;
463                     synchronized (sequenceLock) {
464                         try {
465                             // if input changed again before polling has run, get current input hash from the TV
466                             // first
467                             if (currentInputHash.equals(0L)) {
468                                 CurrentInput polledInput = communicator.getCurrentInput();
469                                 if (!polledInput.getItems().isEmpty()) {
470                                     currentInputHash = polledInput.getItems().get(0).getHashval();
471                                 }
472                             }
473                             communicator
474                                     .changeInput(String.format(MODIFY_STRING_SETTING_JSON, command, currentInputHash));
475                             currentInputHash = 0L;
476                         } catch (VizioException e) {
477                             logger.debug("Unable to set current source on the Vizio TV, source: {}, Exception: {}",
478                                     command, e.getMessage());
479                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
480                                     "@text/offline.communication-error-set-source");
481                         }
482                     }
483                     break;
484                 case ACTIVE_APP:
485                     debounce = false;
486                     synchronized (sequenceLock) {
487                         try {
488                             Optional<VizioApp> selectedApp = userConfigApps.stream()
489                                     .filter(a -> command.toString().equals(a.getName())).findFirst();
490
491                             if (selectedApp.isPresent()) {
492                                 communicator.launchApp(selectedApp.get().getConfig());
493                             } else {
494                                 logger.debug("Unknown app name: '{}', check that it exists in App List configuration",
495                                         command);
496                             }
497                         } catch (VizioException e) {
498                             logger.debug("Unable to launch app name: '{}' on the Vizio TV, Exception: {}", command,
499                                     e.getMessage());
500                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
501                                     "@text/offline.communication-error-launch-app");
502                         }
503                     }
504                     break;
505                 case CONTROL:
506                     debounce = false;
507                     synchronized (sequenceLock) {
508                         try {
509                             handleControlCommand(command);
510                         } catch (VizioException e) {
511                             logger.debug("Unable to send control command: '{}' to the Vizio TV, Exception: {}", command,
512                                     e.getMessage());
513                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
514                                     "@text/offline.communication-error-send-cmd");
515                         }
516                     }
517                     break;
518                 case BUTTON:
519                     synchronized (sequenceLock) {
520                         try {
521                             KeyCommand keyCommand = KeyCommand.valueOf(command.toString().toUpperCase(Locale.ENGLISH));
522                             communicator.sendKeyPress(keyCommand.getJson());
523                         } catch (IllegalArgumentException | VizioException e) {
524                             logger.debug("Unable to send keypress to the Vizio TV, key: {}, Exception: {}", command,
525                                     e.getMessage());
526                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
527                                     "@text/offline.communication-error-send-key");
528                         }
529                     }
530                     break;
531                 default:
532                     logger.warn("Unknown channel: '{}'", channelUID.getId());
533                     break;
534             }
535         }
536     }
537
538     private void handleControlCommand(Command command) throws VizioException {
539         if (command instanceof PlayPauseType) {
540             if (command == PlayPauseType.PLAY) {
541                 communicator.sendKeyPress(KeyCommand.PLAY.getJson());
542             } else if (command == PlayPauseType.PAUSE) {
543                 communicator.sendKeyPress(KeyCommand.PAUSE.getJson());
544             }
545         } else if (command instanceof NextPreviousType) {
546             if (command == NextPreviousType.NEXT) {
547                 communicator.sendKeyPress(KeyCommand.RIGHT.getJson());
548             } else if (command == NextPreviousType.PREVIOUS) {
549                 communicator.sendKeyPress(KeyCommand.LEFT.getJson());
550             }
551         } else if (command instanceof RewindFastforwardType) {
552             if (command == RewindFastforwardType.FASTFORWARD) {
553                 communicator.sendKeyPress(KeyCommand.SEEKFWD.getJson());
554             } else if (command == RewindFastforwardType.REWIND) {
555                 communicator.sendKeyPress(KeyCommand.SEEKBACK.getJson());
556             }
557         } else {
558             logger.warn("Unknown control command: {}", command);
559         }
560     }
561
562     @Override
563     public boolean isLinked(String channelName) {
564         Channel channel = this.thing.getChannel(channelName);
565         if (channel != null) {
566             return isLinked(channel.getUID());
567         } else {
568             return false;
569         }
570     }
571
572     // The remaining methods are used by the console when obtaining the auth token from the TV.
573     public int startPairing(String deviceName) throws VizioException {
574         Random rng = new Random();
575         pairingDeviceId = rng.nextInt(100000);
576
577         pairingToken = communicator.startPairing(deviceName, pairingDeviceId).getItem().getPairingReqToken();
578
579         return pairingToken;
580     }
581
582     public String submitPairingCode(String pairingCode) throws IllegalStateException, VizioException {
583         if (pairingDeviceId < 0 || pairingToken < 0) {
584             throw new IllegalStateException();
585         }
586
587         return communicator.submitPairingCode(pairingDeviceId, pairingCode, pairingToken).getItem().getAuthToken();
588     }
589
590     public void saveAuthToken(String authToken) {
591         pairingDeviceId = -1;
592         pairingToken = -1;
593
594         // Store the auth token in the configuration and restart the thing
595         Configuration configuration = this.getConfig();
596         configuration.put(PROPERTY_AUTH_TOKEN, authToken);
597         this.updateConfiguration(configuration);
598         this.thingUpdated(this.getThing());
599     }
600 }