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