]> git.basschouten.com Git - openhab-addons.git/blob
7ba530ac7e369a55e16525e3375bdb70644f74ec
[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.kaleidescape.internal.handler;
14
15 import static org.openhab.binding.kaleidescape.internal.KaleidescapeBindingConstants.*;
16
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import javax.measure.Unit;
28 import javax.measure.quantity.Time;
29
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.eclipse.jetty.client.HttpClient;
33 import org.openhab.binding.kaleidescape.internal.KaleidescapeException;
34 import org.openhab.binding.kaleidescape.internal.KaleidescapeThingActions;
35 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeConnector;
36 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeDefaultConnector;
37 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeIpConnector;
38 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeMessageEvent;
39 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeMessageEventListener;
40 import org.openhab.binding.kaleidescape.internal.communication.KaleidescapeSerialConnector;
41 import org.openhab.binding.kaleidescape.internal.configuration.KaleidescapeThingConfiguration;
42 import org.openhab.core.io.transport.serial.SerialPortManager;
43 import org.openhab.core.library.types.NextPreviousType;
44 import org.openhab.core.library.types.OnOffType;
45 import org.openhab.core.library.types.PercentType;
46 import org.openhab.core.library.types.PlayPauseType;
47 import org.openhab.core.library.types.RewindFastforwardType;
48 import org.openhab.core.library.types.StringType;
49 import org.openhab.core.library.unit.Units;
50 import org.openhab.core.thing.ChannelUID;
51 import org.openhab.core.thing.Thing;
52 import org.openhab.core.thing.ThingStatus;
53 import org.openhab.core.thing.ThingStatusDetail;
54 import org.openhab.core.thing.ThingTypeUID;
55 import org.openhab.core.thing.binding.BaseThingHandler;
56 import org.openhab.core.thing.binding.ThingHandlerService;
57 import org.openhab.core.types.Command;
58 import org.openhab.core.types.RefreshType;
59 import org.openhab.core.types.State;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 /**
64  * The {@link KaleidescapeHandler} is responsible for handling commands, which are sent to one of the channels.
65  *
66  * Based on the Rotel binding by Laurent Garnier
67  *
68  * @author Michael Lobstein - Initial contribution
69  */
70 @NonNullByDefault
71 public class KaleidescapeHandler extends BaseThingHandler implements KaleidescapeMessageEventListener {
72     private static final long RECON_POLLING_INTERVAL_S = 60;
73     private static final long POLLING_INTERVAL_S = 20;
74
75     private final Logger logger = LoggerFactory.getLogger(KaleidescapeHandler.class);
76     private final SerialPortManager serialPortManager;
77     private final Map<String, String> cache = new HashMap<String, String>();
78
79     protected final HttpClient httpClient;
80     protected final Unit<Time> apiSecondUnit = Units.SECOND;
81
82     private ThingTypeUID thingTypeUID = THING_TYPE_PLAYER;
83     private @Nullable ScheduledFuture<?> reconnectJob;
84     private @Nullable ScheduledFuture<?> pollingJob;
85     private long lastEventReceived = 0;
86     private int updatePeriod = 0;
87
88     protected KaleidescapeConnector connector = new KaleidescapeDefaultConnector();
89     protected int metaRuntimeMultiple = 1;
90     protected int volume = 0;
91     protected boolean volumeEnabled = false;
92     protected boolean isMuted = false;
93     protected boolean isLoadHighlightedDetails = false;
94     protected boolean isLoadAlbumDetails = false;
95     protected String friendlyName = EMPTY;
96     protected Object sequenceLock = new Object();
97
98     public KaleidescapeHandler(Thing thing, SerialPortManager serialPortManager, HttpClient httpClient) {
99         super(thing);
100         this.serialPortManager = serialPortManager;
101         this.httpClient = httpClient;
102     }
103
104     protected void updateChannel(String channelUID, State state) {
105         this.updateState(channelUID, state);
106     }
107
108     protected void updateDetailChannel(String channelUID, State state) {
109         this.updateState(DETAIL + channelUID, state);
110     }
111
112     protected void updateThingProperty(String name, String value) {
113         thing.setProperty(name, value);
114     }
115
116     protected boolean isChannelLinked(String channel) {
117         return isLinked(channel);
118     }
119
120     @Override
121     public void initialize() {
122         final String uid = this.getThing().getUID().getAsString();
123         KaleidescapeThingConfiguration config = getConfigAs(KaleidescapeThingConfiguration.class);
124
125         this.thingTypeUID = thing.getThingTypeUID();
126
127         // Check configuration settings
128         String configError = null;
129         final String serialPort = config.serialPort;
130         final String host = config.host;
131         final Integer port = config.port;
132         final Integer updatePeriod = config.updatePeriod;
133         this.isLoadHighlightedDetails = config.loadHighlightedDetails;
134         this.isLoadAlbumDetails = config.loadAlbumDetails;
135
136         if ((serialPort == null || serialPort.isEmpty()) && (host == null || host.isEmpty())) {
137             configError = "undefined serialPort and host configuration settings; please set one of them";
138         } else if (host == null || host.isEmpty()) {
139             if (serialPort != null && serialPort.toLowerCase().startsWith("rfc2217")) {
140                 configError = "use host and port configuration settings for a serial over IP connection";
141             }
142         } else {
143             if (port == null) {
144                 configError = "undefined port configuration setting";
145             } else if (port <= 0) {
146                 configError = "invalid port configuration setting";
147             }
148         }
149
150         if (configError != null) {
151             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
152             return;
153         }
154
155         if (updatePeriod != null) {
156             this.updatePeriod = updatePeriod;
157         }
158
159         // check if volume is enabled
160         if (config.volumeEnabled) {
161             this.volumeEnabled = true;
162             this.volume = config.initialVolume;
163             this.updateState(VOLUME, new PercentType(this.volume));
164             this.updateState(MUTE, OnOffType.OFF);
165         }
166
167         if (serialPort != null) {
168             connector = new KaleidescapeSerialConnector(serialPortManager, serialPort, uid);
169         } else if (port != null) {
170             connector = new KaleidescapeIpConnector(host, port, uid);
171         } else {
172             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
173                     "Either Serial port or Host & Port must be specifed");
174             return;
175         }
176
177         updateStatus(ThingStatus.UNKNOWN);
178
179         scheduleReconnectJob();
180         schedulePollingJob();
181     }
182
183     @Override
184     public void dispose() {
185         cancelReconnectJob();
186         cancelPollingJob();
187         closeConnection();
188     }
189
190     @Override
191     public Collection<Class<? extends ThingHandlerService>> getServices() {
192         return Collections.singletonList(KaleidescapeThingActions.class);
193     }
194
195     public void handleRawCommand(@Nullable String command) {
196         synchronized (sequenceLock) {
197             try {
198                 connector.sendCommand(command);
199             } catch (KaleidescapeException e) {
200                 logger.warn("K Command: {} failed", command);
201             }
202         }
203     }
204
205     @Override
206     public void handleCommand(ChannelUID channelUID, Command command) {
207         String channel = channelUID.getId();
208
209         if (getThing().getStatus() != ThingStatus.ONLINE) {
210             logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
211             return;
212         }
213         synchronized (sequenceLock) {
214             if (!connector.isConnected()) {
215                 logger.debug("Command {} from channel {} is ignored: connection not established", command, channel);
216                 return;
217             }
218
219             try {
220                 if (command instanceof RefreshType) {
221                     handleRefresh(channel);
222                     return;
223                 }
224
225                 switch (channel) {
226                     case POWER:
227                         if (command instanceof OnOffType) {
228                             connector.sendCommand(command == OnOffType.ON ? LEAVE_STANDBY : ENTER_STANDBY);
229                         }
230                         break;
231                     case VOLUME:
232                         if (command instanceof PercentType) {
233                             this.volume = (int) ((PercentType) command).doubleValue();
234                             logger.debug("Got volume command {}", this.volume);
235                             connector.sendCommand(SEND_EVENT_VOLUME_LEVEL_EQ + this.volume);
236                         }
237                         break;
238                     case MUTE:
239                         if (command instanceof OnOffType) {
240                             this.isMuted = command == OnOffType.ON ? true : false;
241                         }
242                         connector.sendCommand(SEND_EVENT_MUTE + (this.isMuted ? MUTE_ON : MUTE_OFF));
243                         break;
244                     case MUSIC_REPEAT:
245                         if (command instanceof OnOffType) {
246                             connector.sendCommand(command == OnOffType.ON ? MUSIC_REPEAT_ON : MUSIC_REPEAT_OFF);
247                         }
248                         break;
249                     case MUSIC_RANDOM:
250                         if (command instanceof OnOffType) {
251                             connector.sendCommand(command == OnOffType.ON ? MUSIC_RANDOM_ON : MUSIC_RANDOM_OFF);
252                         }
253                         break;
254                     case CONTROL:
255                     case MUSIC_CONTROL:
256                         handleControlCommand(command);
257                         break;
258                     default:
259                         logger.debug("Command {} from channel {} failed: unexpected command", command, channel);
260                         break;
261                 }
262             } catch (KaleidescapeException e) {
263                 logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage());
264                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Sending command failed");
265                 closeConnection();
266                 scheduleReconnectJob();
267             }
268         }
269     }
270
271     /**
272      * Open the connection with the Kaleidescape component
273      *
274      * @return true if the connection is opened successfully or false if not
275      */
276     private synchronized boolean openConnection() {
277         connector.addEventListener(this);
278         try {
279             connector.open();
280         } catch (KaleidescapeException e) {
281             logger.debug("openConnection() failed: {}", e.getMessage());
282         }
283         logger.debug("openConnection(): {}", connector.isConnected() ? "connected" : "disconnected");
284         return connector.isConnected();
285     }
286
287     /**
288      * Close the connection with the Kaleidescape component
289      */
290     private synchronized void closeConnection() {
291         if (connector.isConnected()) {
292             connector.close();
293             connector.removeEventListener(this);
294             logger.debug("closeConnection(): disconnected");
295         }
296     }
297
298     @Override
299     public void onNewMessageEvent(KaleidescapeMessageEvent evt) {
300         lastEventReceived = System.currentTimeMillis();
301
302         // check if we are in standby
303         if (STANDBY_MSG.equals(evt.getKey())) {
304             if (!ThingStatusDetail.BRIDGE_OFFLINE.equals(thing.getStatusInfo().getStatusDetail())) {
305                 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.BRIDGE_OFFLINE, STANDBY_MSG);
306             }
307             return;
308         }
309         try {
310             // Use the Enum valueOf to handle the message based on the event key. Otherwise there would be a huge
311             // case statement here
312             KaleidescapeMessageHandler.valueOf(evt.getKey()).handleMessage(evt.getValue(), this);
313
314             if (!evt.isCached()) {
315                 cache.put(evt.getKey(), evt.getValue());
316             }
317
318             if (ThingStatusDetail.BRIDGE_OFFLINE.equals(thing.getStatusInfo().getStatusDetail())) {
319                 // no longer in standby, update the status
320                 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.friendlyName);
321             }
322         } catch (IllegalArgumentException e) {
323             logger.debug("Unhandled message: key {} = {}", evt.getKey(), evt.getValue());
324         }
325     }
326
327     /**
328      * Schedule the reconnection job
329      */
330     private void scheduleReconnectJob() {
331         logger.debug("Schedule reconnect job");
332         cancelReconnectJob();
333         reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
334             synchronized (sequenceLock) {
335                 if (!connector.isConnected()) {
336                     logger.debug("Trying to reconnect...");
337                     closeConnection();
338                     String error = EMPTY;
339                     if (openConnection()) {
340                         try {
341                             cache.clear();
342                             Set<String> initialCommands = new HashSet<>(Arrays.asList(GET_DEVICE_TYPE_NAME,
343                                     GET_FRIENDLY_NAME, GET_DEVICE_INFO, GET_SYSTEM_VERSION, GET_DEVICE_POWER_STATE,
344                                     GET_CINEMASCAPE_MASK, GET_CINEMASCAPE_MODE, GET_SCALE_MODE, GET_SCREEN_MASK,
345                                     GET_SCREEN_MASK2, GET_VIDEO_MODE, GET_UI_STATE, GET_HIGHLIGHTED_SELECTION,
346                                     GET_CHILD_MODE_STATE, GET_PLAY_STATUS, GET_MOVIE_LOCATION, GET_MOVIE_MEDIA_TYPE,
347                                     GET_PLAYING_TITLE_NAME));
348
349                             // Premiere Players and Cinema One support music
350                             if (thingTypeUID.equals(THING_TYPE_PLAYER) || thingTypeUID.equals(THING_TYPE_CINEMA_ONE)) {
351                                 initialCommands.addAll(Arrays.asList(GET_MUSIC_NOW_PLAYING_STATUS,
352                                         GET_MUSIC_PLAY_STATUS, GET_MUSIC_TITLE));
353                             }
354
355                             // everything after Premiere Player supports GET_SYSTEM_READINESS_STATE
356                             if (!thingTypeUID.equals(THING_TYPE_PLAYER)) {
357                                 initialCommands.add(GET_SYSTEM_READINESS_STATE);
358                             }
359
360                             // only Strato supports the GET_*_COLOR commands
361                             if (thingTypeUID.equals(THING_TYPE_STRATO)) {
362                                 initialCommands.addAll(Arrays.asList(GET_VIDEO_COLOR, GET_CONTENT_COLOR));
363                             }
364
365                             initialCommands.forEach(command -> {
366                                 try {
367                                     connector.sendCommand(command);
368                                 } catch (KaleidescapeException e) {
369                                     logger.debug("{}: {}", "Error sending initial commands", e.getMessage());
370                                 }
371                             });
372
373                             if (this.updatePeriod == 1) {
374                                 connector.sendCommand(SET_STATUS_CUE_PERIOD_1);
375                             }
376                         } catch (KaleidescapeException e) {
377                             error = "First command after connection failed";
378                             logger.debug("{}: {}", error, e.getMessage());
379                             closeConnection();
380                         }
381                     } else {
382                         error = "Reconnection failed";
383                     }
384                     if (!error.equals(EMPTY)) {
385                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
386                         return;
387                     }
388                     updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.friendlyName);
389                     lastEventReceived = System.currentTimeMillis();
390                 }
391             }
392         }, 1, RECON_POLLING_INTERVAL_S, TimeUnit.SECONDS);
393     }
394
395     /**
396      * Cancel the reconnection job
397      */
398     private void cancelReconnectJob() {
399         ScheduledFuture<?> reconnectJob = this.reconnectJob;
400         if (reconnectJob != null) {
401             reconnectJob.cancel(true);
402             this.reconnectJob = null;
403         }
404     }
405
406     /**
407      * Schedule the polling job
408      */
409     private void schedulePollingJob() {
410         logger.debug("Schedule polling job");
411         cancelPollingJob();
412
413         pollingJob = scheduler.scheduleWithFixedDelay(() -> {
414             synchronized (sequenceLock) {
415                 if (connector.isConnected()) {
416                     logger.debug("Polling the component for updated status...");
417                     try {
418                         connector.ping();
419                         cache.clear();
420                     } catch (KaleidescapeException e) {
421                         logger.debug("Polling error: {}", e.getMessage());
422                     }
423
424                     // if the last successful polling update was more than 1.25 intervals ago,
425                     // the component is not responding even though the connection is still good
426                     if ((System.currentTimeMillis() - lastEventReceived) > (POLLING_INTERVAL_S * 1.25 * 1000)) {
427                         logger.debug("Component not responding to status requests");
428                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
429                                 "Component not responding to status requests");
430                         closeConnection();
431                         scheduleReconnectJob();
432                     }
433                 }
434             }
435         }, POLLING_INTERVAL_S, POLLING_INTERVAL_S, TimeUnit.SECONDS);
436     }
437
438     /**
439      * Cancel the polling job
440      */
441     private void cancelPollingJob() {
442         ScheduledFuture<?> pollingJob = this.pollingJob;
443         if (pollingJob != null) {
444             pollingJob.cancel(true);
445             this.pollingJob = null;
446         }
447     }
448
449     private void handleControlCommand(Command command) throws KaleidescapeException {
450         if (command instanceof PlayPauseType) {
451             if (command == PlayPauseType.PLAY) {
452                 connector.sendCommand(PLAY);
453             } else if (command == PlayPauseType.PAUSE) {
454                 connector.sendCommand(PAUSE);
455             }
456         } else if (command instanceof NextPreviousType) {
457             if (command == NextPreviousType.NEXT) {
458                 connector.sendCommand(NEXT);
459             } else if (command == NextPreviousType.PREVIOUS) {
460                 connector.sendCommand(PREVIOUS);
461             }
462         } else if (command instanceof RewindFastforwardType) {
463             if (command == RewindFastforwardType.FASTFORWARD) {
464                 connector.sendCommand(SCAN_FORWARD);
465             } else if (command == RewindFastforwardType.REWIND) {
466                 connector.sendCommand(SCAN_REVERSE);
467             }
468         } else {
469             logger.warn("Unknown control command: {}", command);
470         }
471     }
472
473     private void handleRefresh(String channel) throws KaleidescapeException {
474         switch (channel) {
475             case POWER:
476                 connector.sendCommand(GET_DEVICE_POWER_STATE, cache.get("DEVICE_POWER_STATE"));
477                 break;
478             case VOLUME:
479                 updateState(channel, new PercentType(this.volume));
480                 break;
481             case MUTE:
482                 updateState(channel, this.isMuted ? OnOffType.ON : OnOffType.OFF);
483                 break;
484             case TITLE_NAME:
485                 connector.sendCommand(GET_PLAYING_TITLE_NAME, cache.get("TITLE_NAME"));
486                 break;
487             case PLAY_MODE:
488             case PLAY_SPEED:
489             case TITLE_NUM:
490             case TITLE_LENGTH:
491             case TITLE_LOC:
492             case CHAPTER_NUM:
493             case CHAPTER_LENGTH:
494             case CHAPTER_LOC:
495                 connector.sendCommand(GET_PLAY_STATUS, cache.get("PLAY_STATUS"));
496                 break;
497             case MOVIE_MEDIA_TYPE:
498                 connector.sendCommand(GET_MOVIE_MEDIA_TYPE, cache.get("MOVIE_MEDIA_TYPE"));
499                 break;
500             case MOVIE_LOCATION:
501                 connector.sendCommand(GET_MOVIE_LOCATION, cache.get("MOVIE_LOCATION"));
502                 break;
503             case VIDEO_MODE:
504             case VIDEO_MODE_COMPOSITE:
505             case VIDEO_MODE_COMPONENT:
506             case VIDEO_MODE_HDMI:
507                 connector.sendCommand(GET_VIDEO_MODE, cache.get("VIDEO_MODE"));
508                 break;
509             case VIDEO_COLOR:
510             case VIDEO_COLOR_EOTF:
511                 connector.sendCommand(GET_VIDEO_COLOR, cache.get("VIDEO_COLOR"));
512                 break;
513             case CONTENT_COLOR:
514             case CONTENT_COLOR_EOTF:
515                 connector.sendCommand(GET_CONTENT_COLOR, cache.get("CONTENT_COLOR"));
516                 break;
517             case SCALE_MODE:
518                 connector.sendCommand(GET_SCALE_MODE, cache.get("SCALE_MODE"));
519                 break;
520             case ASPECT_RATIO:
521             case SCREEN_MASK:
522                 connector.sendCommand(GET_SCREEN_MASK, cache.get("SCREEN_MASK"));
523                 break;
524             case SCREEN_MASK2:
525                 connector.sendCommand(GET_SCREEN_MASK2, cache.get("SCREEN_MASK2"));
526                 break;
527             case CINEMASCAPE_MASK:
528                 connector.sendCommand(GET_CINEMASCAPE_MASK, cache.get("GET_CINEMASCAPE_MASK"));
529                 break;
530             case CINEMASCAPE_MODE:
531                 connector.sendCommand(GET_CINEMASCAPE_MODE, cache.get("CINEMASCAPE_MODE"));
532                 break;
533             case UI_STATE:
534                 connector.sendCommand(GET_UI_STATE, cache.get("UI_STATE"));
535                 break;
536             case CHILD_MODE_STATE:
537                 connector.sendCommand(GET_CHILD_MODE_STATE, cache.get("CHILD_MODE_STATE"));
538                 break;
539             case SYSTEM_READINESS_STATE:
540                 connector.sendCommand(GET_SYSTEM_READINESS_STATE, cache.get("SYSTEM_READINESS_STATE"));
541                 break;
542             case HIGHLIGHTED_SELECTION:
543                 connector.sendCommand(GET_HIGHLIGHTED_SELECTION, cache.get("HIGHLIGHTED_SELECTION"));
544                 break;
545             case USER_DEFINED_EVENT:
546             case USER_INPUT:
547             case USER_INPUT_PROMPT:
548                 updateState(channel, StringType.EMPTY);
549                 break;
550             case MUSIC_REPEAT:
551             case MUSIC_RANDOM:
552                 connector.sendCommand(GET_MUSIC_NOW_PLAYING_STATUS, cache.get("MUSIC_NOW_PLAYING_STATUS"));
553                 break;
554             case MUSIC_TRACK:
555             case MUSIC_ARTIST:
556             case MUSIC_ALBUM:
557             case MUSIC_TRACK_HANDLE:
558             case MUSIC_ALBUM_HANDLE:
559             case MUSIC_NOWPLAY_HANDLE:
560                 connector.sendCommand(GET_MUSIC_TITLE, cache.get("MUSIC_TITLE"));
561                 break;
562             case MUSIC_PLAY_MODE:
563             case MUSIC_PLAY_SPEED:
564             case MUSIC_TRACK_LENGTH:
565             case MUSIC_TRACK_POSITION:
566             case MUSIC_TRACK_PROGRESS:
567                 connector.sendCommand(GET_MUSIC_PLAY_STATUS, cache.get("MUSIC_PLAY_STATUS"));
568                 break;
569             case DETAIL_TYPE:
570             case DETAIL_TITLE:
571             case DETAIL_ALBUM_TITLE:
572             case DETAIL_COVER_ART:
573             case DETAIL_COVER_URL:
574             case DETAIL_HIRES_COVER_URL:
575             case DETAIL_RATING:
576             case DETAIL_YEAR:
577             case DETAIL_RUNNING_TIME:
578             case DETAIL_ACTORS:
579             case DETAIL_ARTIST:
580             case DETAIL_DIRECTORS:
581             case DETAIL_GENRES:
582             case DETAIL_RATING_REASON:
583             case DETAIL_SYNOPSIS:
584             case DETAIL_REVIEW:
585             case DETAIL_COLOR_DESCRIPTION:
586             case DETAIL_COUNTRY:
587             case DETAIL_ASPECT_RATIO:
588             case DETAIL_DISC_LOCATION:
589                 updateState(channel, StringType.EMPTY);
590                 break;
591         }
592     }
593 }