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