]> git.basschouten.com Git - openhab-addons.git/blob
b5f80658552c264520f63bf1539bbae08391b764
[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.bosesoundtouch.internal.handler;
14
15 import static org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.URI;
19 import java.util.Arrays;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.List;
23 import java.util.Objects;
24 import java.util.concurrent.Future;
25 import java.util.concurrent.ScheduledExecutorService;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28 import java.util.stream.Collectors;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.eclipse.jetty.websocket.api.Session;
33 import org.eclipse.jetty.websocket.api.StatusCode;
34 import org.eclipse.jetty.websocket.api.WebSocketFrameListener;
35 import org.eclipse.jetty.websocket.api.WebSocketListener;
36 import org.eclipse.jetty.websocket.api.extensions.Frame;
37 import org.eclipse.jetty.websocket.api.extensions.Frame.Type;
38 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
39 import org.eclipse.jetty.websocket.client.WebSocketClient;
40 import org.openhab.binding.bosesoundtouch.internal.APIRequest;
41 import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchConfiguration;
42 import org.openhab.binding.bosesoundtouch.internal.BoseSoundTouchNotificationChannelConfiguration;
43 import org.openhab.binding.bosesoundtouch.internal.BoseStateDescriptionOptionProvider;
44 import org.openhab.binding.bosesoundtouch.internal.CommandExecutor;
45 import org.openhab.binding.bosesoundtouch.internal.OperationModeType;
46 import org.openhab.binding.bosesoundtouch.internal.PresetContainer;
47 import org.openhab.binding.bosesoundtouch.internal.RemoteKeyType;
48 import org.openhab.binding.bosesoundtouch.internal.XMLResponseProcessor;
49 import org.openhab.core.library.types.DecimalType;
50 import org.openhab.core.library.types.NextPreviousType;
51 import org.openhab.core.library.types.OnOffType;
52 import org.openhab.core.library.types.PercentType;
53 import org.openhab.core.library.types.PlayPauseType;
54 import org.openhab.core.library.types.StringType;
55 import org.openhab.core.thing.Channel;
56 import org.openhab.core.thing.ChannelUID;
57 import org.openhab.core.thing.Thing;
58 import org.openhab.core.thing.ThingStatus;
59 import org.openhab.core.thing.ThingStatusDetail;
60 import org.openhab.core.thing.ThingTypeUID;
61 import org.openhab.core.thing.binding.BaseThingHandler;
62 import org.openhab.core.thing.binding.ThingHandlerCallback;
63 import org.openhab.core.thing.type.ChannelTypeUID;
64 import org.openhab.core.types.Command;
65 import org.openhab.core.types.RefreshType;
66 import org.openhab.core.types.State;
67 import org.openhab.core.types.StateOption;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 /**
72  * The {@link BoseSoundTouchHandler} is responsible for handling commands, which are
73  * sent to one of the channels.
74  *
75  * @author Christian Niessner - Initial contribution
76  * @author Thomas Traunbauer - Initial contribution
77  * @author Kai Kreuzer - code clean up
78  * @author Alexander Kostadinov - Handling of websocket ping-pong mechanism for thing status check
79  */
80 @NonNullByDefault
81 public class BoseSoundTouchHandler extends BaseThingHandler implements WebSocketListener, WebSocketFrameListener {
82
83     private static final int MAX_MISSED_PONGS_COUNT = 2;
84
85     private static final int RETRY_INTERVAL_IN_SECS = 30;
86
87     private final Logger logger = LoggerFactory.getLogger(BoseSoundTouchHandler.class);
88
89     private @Nullable ScheduledFuture<?> connectionChecker;
90     private @Nullable WebSocketClient client;
91     private @Nullable volatile Session session;
92     private @Nullable volatile CommandExecutor commandExecutor;
93     private volatile int missedPongsCount = 0;
94
95     private XMLResponseProcessor xmlResponseProcessor;
96
97     private PresetContainer presetContainer;
98     private BoseStateDescriptionOptionProvider stateOptionProvider;
99
100     private @Nullable Future<?> sessionFuture;
101
102     /**
103      * Creates a new instance of this class for the {@link Thing}.
104      *
105      * @param thing the thing that should be handled, not null
106      * @param presetContainer the preset container instance to use for managing presets
107      *
108      * @throws IllegalArgumentException if thing or factory argument is null
109      */
110     public BoseSoundTouchHandler(Thing thing, PresetContainer presetContainer,
111             BoseStateDescriptionOptionProvider stateOptionProvider) {
112         super(thing);
113         this.presetContainer = presetContainer;
114         this.stateOptionProvider = stateOptionProvider;
115         xmlResponseProcessor = new XMLResponseProcessor(this);
116     }
117
118     @Override
119     public void initialize() {
120         connectionChecker = scheduler.scheduleWithFixedDelay(() -> checkConnection(), 0, RETRY_INTERVAL_IN_SECS,
121                 TimeUnit.SECONDS);
122     }
123
124     @Override
125     public void dispose() {
126         ScheduledFuture<?> localConnectionChecker = connectionChecker;
127         if (localConnectionChecker != null) {
128             if (!localConnectionChecker.isCancelled()) {
129                 localConnectionChecker.cancel(true);
130                 connectionChecker = null;
131             }
132         }
133         closeConnection();
134         super.dispose();
135     }
136
137     @Override
138     public void handleRemoval() {
139         presetContainer.clear();
140         super.handleRemoval();
141     }
142
143     @Override
144     public void updateState(String channelID, State state) {
145         // don't update channel if it's not linked (in case of Stereo Pair slave device)
146         if (isLinked(channelID)) {
147             super.updateState(channelID, state);
148         } else {
149             logger.debug("{}: Skipping state update because of not linked channel '{}'", getDeviceName(), channelID);
150         }
151     }
152
153     @Override
154     public void handleCommand(ChannelUID channelUID, Command command) {
155         CommandExecutor localCommandExecutor = commandExecutor;
156         if (localCommandExecutor == null) {
157             logger.debug("{}: Can't handle command '{}' for channel '{}' because of not initialized connection.",
158                     getDeviceName(), command, channelUID);
159             return;
160         } else {
161             logger.debug("{}: handleCommand({}, {});", getDeviceName(), channelUID, command);
162         }
163
164         if (command.equals(RefreshType.REFRESH)) {
165             switch (channelUID.getIdWithoutGroup()) {
166                 case CHANNEL_BASS:
167                     localCommandExecutor.getInformations(APIRequest.BASS);
168                     break;
169                 case CHANNEL_KEY_CODE:
170                     // refresh makes no sense... ?
171                     break;
172                 case CHANNEL_NOWPLAYING_ALBUM:
173                 case CHANNEL_NOWPLAYING_ARTIST:
174                 case CHANNEL_NOWPLAYING_ARTWORK:
175                 case CHANNEL_NOWPLAYING_DESCRIPTION:
176                 case CHANNEL_NOWPLAYING_GENRE:
177                 case CHANNEL_NOWPLAYING_ITEMNAME:
178                 case CHANNEL_NOWPLAYING_STATIONLOCATION:
179                 case CHANNEL_NOWPLAYING_STATIONNAME:
180                 case CHANNEL_NOWPLAYING_TRACK:
181                 case CHANNEL_RATEENABLED:
182                 case CHANNEL_SKIPENABLED:
183                 case CHANNEL_SKIPPREVIOUSENABLED:
184                     localCommandExecutor.getInformations(APIRequest.NOW_PLAYING);
185                     break;
186                 case CHANNEL_VOLUME:
187                     localCommandExecutor.getInformations(APIRequest.VOLUME);
188                     break;
189                 default:
190                     logger.debug("{} : Got command '{}' for channel '{}' which is unhandled!", getDeviceName(), command,
191                             channelUID.getId());
192             }
193             return;
194         }
195         switch (channelUID.getIdWithoutGroup()) {
196             case CHANNEL_POWER:
197                 if (command instanceof OnOffType) {
198                     localCommandExecutor.postPower((OnOffType) command);
199                 } else {
200                     logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
201                 }
202                 break;
203             case CHANNEL_VOLUME:
204                 if (command instanceof PercentType) {
205                     localCommandExecutor.postVolume((PercentType) command);
206                 } else {
207                     logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
208                 }
209                 break;
210             case CHANNEL_MUTE:
211                 if (command instanceof OnOffType) {
212                     localCommandExecutor.postVolumeMuted((OnOffType) command);
213                 } else {
214                     logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
215                 }
216                 break;
217             case CHANNEL_OPERATIONMODE:
218                 if (command instanceof StringType) {
219                     String cmd = command.toString().toUpperCase().trim();
220                     try {
221                         OperationModeType mode = OperationModeType.valueOf(cmd);
222                         localCommandExecutor.postOperationMode(mode);
223                     } catch (IllegalArgumentException iae) {
224                         logger.warn("{}: OperationMode \"{}\" is not valid!", getDeviceName(), cmd);
225                     }
226                 }
227                 break;
228             case CHANNEL_PLAYER_CONTROL:
229                 if ((command instanceof PlayPauseType) || (command instanceof NextPreviousType)) {
230                     localCommandExecutor.postPlayerControl(command);
231                 } else {
232                     logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
233                 }
234                 break;
235             case CHANNEL_PRESET:
236                 if (command instanceof DecimalType) {
237                     localCommandExecutor.postPreset((DecimalType) command);
238                 } else {
239                     logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
240                 }
241                 break;
242             case CHANNEL_BASS:
243                 if (command instanceof DecimalType) {
244                     localCommandExecutor.postBass((DecimalType) command);
245                 } else {
246                     logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
247                 }
248                 break;
249             case CHANNEL_SAVE_AS_PRESET:
250                 if (command instanceof DecimalType) {
251                     localCommandExecutor.addCurrentContentItemToPresetContainer((DecimalType) command);
252                 } else {
253                     logger.debug("{}: Unhandled command type: {}: {}", getDeviceName(), command.getClass(), command);
254                 }
255                 break;
256             case CHANNEL_KEY_CODE:
257                 if (command instanceof StringType) {
258                     String cmd = command.toString().toUpperCase().trim();
259                     try {
260                         RemoteKeyType keyCommand = RemoteKeyType.valueOf(cmd);
261                         localCommandExecutor.postRemoteKey(keyCommand);
262                     } catch (IllegalArgumentException e) {
263                         logger.debug("{}: Unhandled remote key: {}", getDeviceName(), cmd);
264                     }
265                 }
266                 break;
267             default:
268                 Channel channel = getThing().getChannel(channelUID.getId());
269                 if (channel != null) {
270                     ChannelTypeUID chTypeUid = channel.getChannelTypeUID();
271                     if (chTypeUid != null) {
272                         switch (chTypeUid.getId()) {
273                             case CHANNEL_NOTIFICATION_SOUND:
274                                 String appKey = Objects.toString(getConfig().get(BoseSoundTouchConfiguration.APP_KEY),
275                                         null);
276                                 if (appKey != null && !appKey.isEmpty()) {
277                                     if (command instanceof StringType) {
278                                         String url = command.toString();
279                                         BoseSoundTouchNotificationChannelConfiguration notificationConfiguration = channel
280                                                 .getConfiguration()
281                                                 .as(BoseSoundTouchNotificationChannelConfiguration.class);
282                                         if (!url.isEmpty()) {
283                                             localCommandExecutor.playNotificationSound(appKey,
284                                                     notificationConfiguration, url);
285                                         }
286                                     }
287                                 } else {
288                                     logger.warn("Missing app key - cannot use notification api");
289                                 }
290                                 return;
291                         }
292                     }
293                 }
294                 logger.warn("{} : Got command '{}' for channel '{}' which is unhandled!", getDeviceName(), command,
295                         channelUID.getId());
296                 break;
297         }
298     }
299
300     /**
301      * Returns the CommandExecutor of this handler
302      *
303      * @return the CommandExecutor of this handler
304      */
305     public @Nullable CommandExecutor getCommandExecutor() {
306         return commandExecutor;
307     }
308
309     /**
310      * Sets the CommandExecutor of this handler
311      *
312      */
313     public void setCommandExecutor(@Nullable CommandExecutor commandExecutor) {
314         this.commandExecutor = commandExecutor;
315     }
316
317     /**
318      * Returns the Session this handler has opened
319      *
320      * @return the Session this handler has opened
321      */
322     public @Nullable Session getSession() {
323         return session;
324     }
325
326     /**
327      * Returns the name of the device delivered from itself
328      *
329      * @return the name of the device delivered from itself
330      */
331     public @Nullable String getDeviceName() {
332         return getThing().getProperties().get(DEVICE_INFO_NAME);
333     }
334
335     /**
336      * Returns the type of the device delivered from itself
337      *
338      * @return the type of the device delivered from itself
339      */
340     public @Nullable String getDeviceType() {
341         return getThing().getProperties().get(DEVICE_INFO_TYPE);
342     }
343
344     /**
345      * Returns the MAC Address of this device
346      *
347      * @return the MAC Address of this device (in format "123456789ABC")
348      */
349     public @Nullable String getMacAddress() {
350         return ((String) getThing().getConfiguration().get(BoseSoundTouchConfiguration.MAC_ADDRESS)).replaceAll(":",
351                 "");
352     }
353
354     /**
355      * Returns the IP Address of this device
356      *
357      * @return the IP Address of this device
358      */
359     public @Nullable String getIPAddress() {
360         return (String) getThing().getConfiguration().getProperties().get(BoseSoundTouchConfiguration.HOST);
361     }
362
363     /**
364      * Provides the handler internal scheduler instance
365      *
366      * @return the {@link ScheduledExecutorService} instance used by this handler
367      */
368     public ScheduledExecutorService getScheduler() {
369         return scheduler;
370     }
371
372     public PresetContainer getPresetContainer() {
373         return this.presetContainer;
374     }
375
376     @Override
377     public void onWebSocketConnect(@Nullable Session session) {
378         logger.debug("{}: onWebSocketConnect('{}')", getDeviceName(), session);
379         this.session = session;
380         commandExecutor = new CommandExecutor(this);
381         updateStatus(ThingStatus.ONLINE);
382     }
383
384     @Override
385     public void onWebSocketError(@Nullable Throwable e) {
386         Throwable localThrowable = (e != null) ? e
387                 : new IllegalStateException("Null Exception passed to onWebSocketError");
388         logger.debug("{}: Error during websocket communication: {}", getDeviceName(), localThrowable.getMessage(),
389                 localThrowable);
390         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, localThrowable.getMessage());
391         CommandExecutor localCommandExecutor = commandExecutor;
392         if (localCommandExecutor != null) {
393             localCommandExecutor.postOperationMode(OperationModeType.OFFLINE);
394             commandExecutor = null;
395         }
396         Session localSession = session;
397         if (localSession != null) {
398             localSession.close(StatusCode.SERVER_ERROR, getDeviceName() + ": Failure: " + localThrowable.getMessage());
399             session = null;
400         }
401     }
402
403     @Override
404     public void onWebSocketText(@Nullable String msg) {
405         logger.debug("{}: onWebSocketText('{}')", getDeviceName(), msg);
406         try {
407             String localMessage = msg;
408             if (localMessage != null) {
409                 xmlResponseProcessor.handleMessage(localMessage);
410             }
411         } catch (Exception e) {
412             logger.warn("{}: Could not parse XML from string '{}'.", getDeviceName(), msg, e);
413         }
414     }
415
416     @Override
417     public void onWebSocketBinary(byte @Nullable [] payload, int offset, int len) {
418         // we don't expect binary data so just dump if we get some...
419         logger.debug("{}: onWebSocketBinary({}, {}, '{}')", getDeviceName(), offset, len, Arrays.toString(payload));
420     }
421
422     @Override
423     public void onWebSocketClose(int code, @Nullable String reason) {
424         logger.debug("{}: onClose({}, '{}')", getDeviceName(), code, reason);
425         missedPongsCount = 0;
426         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, reason);
427         CommandExecutor localCommandExecutor = commandExecutor;
428         if (localCommandExecutor != null) {
429             localCommandExecutor.postOperationMode(OperationModeType.OFFLINE);
430         }
431     }
432
433     @Override
434     public void onWebSocketFrame(@Nullable Frame frame) {
435         Frame localFrame = frame;
436         if (localFrame != null) {
437             if (localFrame.getType() == Type.PONG) {
438                 missedPongsCount = 0;
439             }
440         }
441     }
442
443     private synchronized void openConnection() {
444         closeConnection();
445         try {
446             WebSocketClient localClient = new WebSocketClient();
447             // we need longer timeouts for web socket.
448             localClient.setMaxIdleTimeout(360 * 1000);
449             // Port seems to be hard coded, therefore no user input or discovery is necessary
450             String wsUrl = "ws://" + getIPAddress() + ":8080/";
451             logger.debug("{}: Connecting to: {}", getDeviceName(), wsUrl);
452             ClientUpgradeRequest request = new ClientUpgradeRequest();
453             request.setSubProtocols("gabbo");
454             localClient.setStopTimeout(1000);
455             localClient.start();
456             sessionFuture = localClient.connect(this, new URI(wsUrl), request);
457             client = localClient;
458         } catch (Exception e) {
459             onWebSocketError(e);
460         }
461     }
462
463     private synchronized void closeConnection() {
464         Session localSession = this.session;
465         if (localSession != null) {
466             try {
467                 localSession.close(StatusCode.NORMAL, "Binding shutdown");
468             } catch (Exception e) {
469                 logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(),
470                         e.getClass().getName(), e.getMessage());
471             }
472             session = null;
473         }
474         Future<?> localSessionFuture = sessionFuture;
475         if (localSessionFuture != null) {
476             if (!localSessionFuture.isDone()) {
477                 localSessionFuture.cancel(true);
478             }
479         }
480         WebSocketClient localClient = client;
481         if (localClient != null) {
482             try {
483                 localClient.stop();
484                 localClient.destroy();
485             } catch (Exception e) {
486                 logger.debug("{}: Error while closing websocket communication: {} ({})", getDeviceName(),
487                         e.getClass().getName(), e.getMessage());
488             }
489             client = null;
490         }
491
492         commandExecutor = null;
493     }
494
495     private void checkConnection() {
496         if (getThing().getStatus() != ThingStatus.ONLINE || session == null || client == null
497                 || commandExecutor == null) {
498             openConnection(); // try to reconnect....
499         }
500         Session localSession = this.session;
501         if (localSession != null) {
502             if (getThing().getStatus() == ThingStatus.ONLINE && localSession.isOpen()) {
503                 try {
504                     localSession.getRemote().sendPing(null);
505                     missedPongsCount++;
506                 } catch (IOException e) {
507                     onWebSocketError(e);
508                     closeConnection();
509                     openConnection();
510                 }
511
512                 if (missedPongsCount >= MAX_MISSED_PONGS_COUNT) {
513                     logger.debug("{}: Closing connection because of too many missed PONGs: {} (max allowed {}) ",
514                             getDeviceName(), missedPongsCount, MAX_MISSED_PONGS_COUNT);
515                     missedPongsCount = 0;
516                     closeConnection();
517                     openConnection();
518                 }
519             }
520         }
521     }
522
523     public void refreshPresetChannel() {
524         List<StateOption> stateOptions = presetContainer.getAllPresets().stream().map(e -> e.toStateOption())
525                 .sorted(Comparator.comparing(StateOption::getValue)).collect(Collectors.toList());
526         stateOptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_PRESET), stateOptions);
527     }
528
529     public void handleGroupUpdated(BoseSoundTouchConfiguration masterPlayerConfiguration) {
530         String deviceId = getMacAddress();
531
532         if (masterPlayerConfiguration.macAddress != null) {
533             // Stereo pair
534             if (Objects.equals(masterPlayerConfiguration.macAddress, deviceId)) {
535                 if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {
536                     logger.debug("{}: Stereo Pair was created and this is the master device.", getDeviceName());
537                 } else {
538                     logger.debug("{}: Unsupported operation for player of type: {}", getDeviceName(),
539                             getThing().getThingTypeUID());
540                 }
541             } else {
542                 if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {
543                     logger.debug("{}: Stereo Pair was created and this is NOT the master device.", getDeviceName());
544                     updateThing(editThing().withChannels(Collections.emptyList()).build());
545                 } else {
546                     logger.debug("{}: Unsupported operation for player of type: {}", getDeviceName(),
547                             getThing().getThingTypeUID());
548                 }
549             }
550         } else {
551             // NO Stereo Pair
552             if (getThing().getThingTypeUID().equals(BST_10_THING_TYPE_UID)) {
553                 if (getThing().getChannels().isEmpty()) {
554                     logger.debug("{}: Stereo Pair was disbounded. Restoring channels", getDeviceName());
555                     updateThing(editThing().withChannels(getAllChannels(BST_10_THING_TYPE_UID)).build());
556                 } else {
557                     logger.debug("{}: Stereo Pair was disbounded.", getDeviceName());
558                 }
559             } else {
560                 logger.debug("{}: Unsupported operation for player of type: {}", getDeviceName(),
561                         getThing().getThingTypeUID());
562             }
563         }
564     }
565
566     private List<Channel> getAllChannels(ThingTypeUID thingTypeUID) {
567         ThingHandlerCallback callback = getCallback();
568         if (callback == null) {
569             return Collections.emptyList();
570         }
571
572         return CHANNEL_IDS.stream()
573                 .map(channelId -> callback.createChannelBuilder(new ChannelUID(getThing().getUID(), channelId),
574                         createChannelTypeUID(thingTypeUID, channelId)).build())
575                 .collect(Collectors.toList());
576     }
577
578     private ChannelTypeUID createChannelTypeUID(ThingTypeUID thingTypeUID, String channelId) {
579         if (CHANNEL_OPERATIONMODE.equals(channelId)) {
580             return createOperationModeChannelTypeUID(thingTypeUID);
581         }
582
583         return new ChannelTypeUID(BINDING_ID, channelId);
584     }
585
586     private ChannelTypeUID createOperationModeChannelTypeUID(ThingTypeUID thingTypeUID) {
587         String channelTypeId = CHANNEL_TYPE_OPERATION_MODE_DEFAULT;
588
589         if (BST_10_THING_TYPE_UID.equals(thingTypeUID) || BST_20_THING_TYPE_UID.equals(thingTypeUID)
590                 || BST_30_THING_TYPE_UID.equals(thingTypeUID)) {
591             channelTypeId = CHANNEL_TYPE_OPERATION_MODE_BST_10_20_30;
592         } else if (BST_300_THING_TYPE_UID.equals(thingTypeUID)) {
593             channelTypeId = CHANNEL_TYPE_OPERATION_MODE_BST_300;
594         } else if (BST_SA5A_THING_TYPE_UID.equals(thingTypeUID)) {
595             channelTypeId = CHANNEL_TYPE_OPERATION_MODE_BST_SA5A;
596         } else if (BST_WLA_THING_TYPE_UID.equals(thingTypeUID)) {
597             channelTypeId = CHANNEL_TYPE_OPERATION_MODE_BST_WLA;
598         } else if (BST_WSMS_THING_TYPE_UID.equals(thingTypeUID)) {
599             channelTypeId = CHANNEL_TYPE_OPERATION_MODE_DEFAULT;
600         }
601
602         return new ChannelTypeUID(BINDING_ID, channelTypeId);
603     }
604 }