2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.oppo.internal.handler;
15 import static org.openhab.binding.oppo.internal.OppoBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.oppo.internal.OppoException;
28 import org.openhab.binding.oppo.internal.OppoStateDescriptionOptionProvider;
29 import org.openhab.binding.oppo.internal.communication.OppoCommand;
30 import org.openhab.binding.oppo.internal.communication.OppoConnector;
31 import org.openhab.binding.oppo.internal.communication.OppoDefaultConnector;
32 import org.openhab.binding.oppo.internal.communication.OppoIpConnector;
33 import org.openhab.binding.oppo.internal.communication.OppoMessageEvent;
34 import org.openhab.binding.oppo.internal.communication.OppoMessageEventListener;
35 import org.openhab.binding.oppo.internal.communication.OppoSerialConnector;
36 import org.openhab.binding.oppo.internal.communication.OppoStatusCodes;
37 import org.openhab.binding.oppo.internal.configuration.OppoThingConfiguration;
38 import org.openhab.core.io.transport.serial.SerialPortManager;
39 import org.openhab.core.library.types.DecimalType;
40 import org.openhab.core.library.types.NextPreviousType;
41 import org.openhab.core.library.types.OnOffType;
42 import org.openhab.core.library.types.PercentType;
43 import org.openhab.core.library.types.PlayPauseType;
44 import org.openhab.core.library.types.QuantityType;
45 import org.openhab.core.library.types.RewindFastforwardType;
46 import org.openhab.core.library.types.StringType;
47 import org.openhab.core.library.unit.SmartHomeUnits;
48 import org.openhab.core.thing.Channel;
49 import org.openhab.core.thing.ChannelUID;
50 import org.openhab.core.thing.Thing;
51 import org.openhab.core.thing.ThingStatus;
52 import org.openhab.core.thing.ThingStatusDetail;
53 import org.openhab.core.thing.binding.BaseThingHandler;
54 import org.openhab.core.types.Command;
55 import org.openhab.core.types.State;
56 import org.openhab.core.types.StateOption;
57 import org.openhab.core.types.UnDefType;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
62 * The {@link OppoHandler} is responsible for handling commands, which are sent to one of the channels.
64 * Based on the Rotel binding by Laurent Garnier
66 * @author Michael Lobstein - Initial contribution
69 public class OppoHandler extends BaseThingHandler implements OppoMessageEventListener {
70 private static final long RECON_POLLING_INTERVAL_SEC = 60;
71 private static final long POLLING_INTERVAL_SEC = 15;
72 private static final long INITIAL_POLLING_DELAY_SEC = 10;
73 private static final long SLEEP_BETWEEN_CMD_MS = 100;
75 private static final Pattern TIME_CODE_PATTERN = Pattern
76 .compile("^(\\d{3}) (\\d{3}) ([A-Z]{1}) (\\d{2}:\\d{2}:\\d{2})$");
78 private final Logger logger = LoggerFactory.getLogger(OppoHandler.class);
80 private @Nullable ScheduledFuture<?> reconnectJob;
81 private @Nullable ScheduledFuture<?> pollingJob;
83 private OppoStateDescriptionOptionProvider stateDescriptionProvider;
84 private SerialPortManager serialPortManager;
85 private OppoConnector connector = new OppoDefaultConnector();
87 private List<StateOption> inputSourceOptions = new ArrayList<>();
88 private List<StateOption> hdmiModeOptions = new ArrayList<>();
90 private long lastEventReceived = System.currentTimeMillis();
91 private String versionString = BLANK;
92 private String verboseMode = VERBOSE_2;
93 private String currentChapter = BLANK;
94 private String currentTimeMode = T;
95 private String currentPlayMode = BLANK;
96 private String currentDiscType = BLANK;
97 private boolean isPowerOn = false;
98 private boolean isUDP20X = false;
99 private boolean isBdpIP = false;
100 private Object sequenceLock = new Object();
105 public OppoHandler(Thing thing, OppoStateDescriptionOptionProvider stateDescriptionProvider,
106 SerialPortManager serialPortManager) {
108 this.stateDescriptionProvider = stateDescriptionProvider;
109 this.serialPortManager = serialPortManager;
113 public void initialize() {
114 OppoThingConfiguration config = getConfigAs(OppoThingConfiguration.class);
115 final String uid = this.getThing().getUID().getAsString();
117 // Check configuration settings
118 String configError = null;
119 boolean override = false;
121 Integer model = config.model;
122 String serialPort = config.serialPort;
123 String host = config.host;
124 Integer port = config.port;
127 configError = "player model must be specified";
131 if ((serialPort == null || serialPort.isEmpty()) && (host == null || host.isEmpty())) {
132 configError = "undefined serialPort and host configuration settings; please set one of them";
133 } else if (serialPort != null && (host == null || host.isEmpty())) {
134 if (serialPort.toLowerCase().startsWith("rfc2217")) {
135 configError = "use host and port configuration settings for a serial over IP connection";
139 if (model == MODEL83) {
143 } else if (model == MODEL103 || model == MODEL105) {
150 } else if (port <= 0) {
151 configError = "invalid port configuration setting";
155 if (configError != null) {
156 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
160 if (serialPort != null) {
161 connector = new OppoSerialConnector(serialPortManager, serialPort, uid);
162 } else if (port != null) {
163 connector = new OppoIpConnector(host, port, uid);
164 connector.overrideCmdPreamble(override);
166 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
167 "Either Serial port or Host & Port must be specifed");
171 if (config.verboseMode) {
172 this.verboseMode = VERBOSE_3;
175 if (model == MODEL203 || model == MODEL205) {
176 this.isUDP20X = true;
179 this.buildOptionDropdowns(model);
180 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SOURCE),
182 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_HDMI_MODE),
185 // remove channels not needed for this model
186 List<Channel> channels = new ArrayList<>(this.getThing().getChannels());
188 if (model == MODEL83) {
189 channels.removeIf(c -> (c.getUID().getId().equals(CHANNEL_SUB_SHIFT)
190 || c.getUID().getId().equals(CHANNEL_OSD_POSITION)));
193 if (model == MODEL83 || model == MODEL103 || model == MODEL105) {
194 channels.removeIf(c -> (c.getUID().getId().equals(CHANNEL_ASPECT_RATIO)
195 || c.getUID().getId().equals(CHANNEL_HDR_MODE)));
198 // no query to determine this, so set the default value at startup
199 updateChannelState(CHANNEL_TIME_MODE, currentTimeMode);
201 updateThing(editThing().withChannels(channels).build());
203 scheduleReconnectJob();
204 schedulePollingJob();
206 updateStatus(ThingStatus.UNKNOWN);
210 public void dispose() {
211 cancelReconnectJob();
218 * Handle a command the UI
220 * @param channelUID the channel sending the command
221 * @param command the command received
225 public void handleCommand(ChannelUID channelUID, Command command) {
226 String channel = channelUID.getId();
228 if (getThing().getStatus() != ThingStatus.ONLINE) {
229 logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
233 if (!connector.isConnected()) {
234 logger.debug("Command {} from channel {} is ignored: connection not established", command, channel);
238 synchronized (sequenceLock) {
240 String commandStr = command.toString();
243 if (command instanceof OnOffType) {
244 connector.sendCommand(
245 command == OnOffType.ON ? OppoCommand.POWER_ON : OppoCommand.POWER_OFF);
246 isPowerOn = (command == OnOffType.ON ? true : false);
250 if (command instanceof PercentType) {
251 connector.sendCommand(OppoCommand.SET_VOLUME_LEVEL, commandStr);
255 if (command instanceof OnOffType) {
256 if (command == OnOffType.ON) {
257 connector.sendCommand(OppoCommand.SET_VOLUME_LEVEL, MUTE);
259 connector.sendCommand(OppoCommand.MUTE);
264 if (command instanceof DecimalType) {
265 int value = ((DecimalType) command).intValue();
266 connector.sendCommand(OppoCommand.SET_INPUT_SOURCE, String.valueOf(value));
269 case CHANNEL_CONTROL:
270 this.handleControlCommand(command);
272 case CHANNEL_TIME_MODE:
273 if (command instanceof StringType) {
274 connector.sendCommand(OppoCommand.SET_TIME_DISPLAY, commandStr);
275 currentTimeMode = commandStr;
278 case CHANNEL_REPEAT_MODE:
279 if (command instanceof StringType) {
280 // this one is lame, the response code when querying repeat mode is two digits,
281 // but setting it is a 2-3 letter code.
282 connector.sendCommand(OppoCommand.SET_REPEAT, OppoStatusCodes.REPEAT_MODE.get(commandStr));
285 case CHANNEL_ZOOM_MODE:
286 if (command instanceof StringType) {
287 // again why could't they make the query code and set code the same?
288 connector.sendCommand(OppoCommand.SET_ZOOM_RATIO,
289 OppoStatusCodes.ZOOM_MODE.get(commandStr));
292 case CHANNEL_SUB_SHIFT:
293 if (command instanceof DecimalType) {
294 int value = ((DecimalType) command).intValue();
295 connector.sendCommand(OppoCommand.SET_SUBTITLE_SHIFT, String.valueOf(value));
298 case CHANNEL_OSD_POSITION:
299 if (command instanceof DecimalType) {
300 int value = ((DecimalType) command).intValue();
301 connector.sendCommand(OppoCommand.SET_OSD_POSITION, String.valueOf(value));
304 case CHANNEL_HDMI_MODE:
305 if (command instanceof StringType) {
306 connector.sendCommand(OppoCommand.SET_HDMI_MODE, commandStr);
309 case CHANNEL_HDR_MODE:
310 if (command instanceof StringType) {
311 connector.sendCommand(OppoCommand.SET_HDR_MODE, commandStr);
314 case CHANNEL_REMOTE_BUTTON:
315 if (command instanceof StringType) {
316 connector.sendCommand(commandStr);
320 logger.warn("Unknown Command {} from channel {}", command, channel);
323 } catch (OppoException e) {
324 logger.warn("Command {} from channel {} failed: {}", command, channel, e.getMessage());
325 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Sending command failed");
327 scheduleReconnectJob();
333 * Open the connection with the Oppo player
335 * @return true if the connection is opened successfully or false if not
337 private synchronized boolean openConnection() {
338 connector.addEventListener(this);
341 } catch (OppoException e) {
342 logger.debug("openConnection() failed: {}", e.getMessage());
344 logger.debug("openConnection(): {}", connector.isConnected() ? "connected" : "disconnected");
345 return connector.isConnected();
349 * Close the connection with the Oppo player
351 private synchronized void closeConnection() {
352 if (connector.isConnected()) {
354 connector.removeEventListener(this);
355 logger.debug("closeConnection(): disconnected");
360 * Handle an event received from the Oppo player
362 * @param event the event to process
365 public void onNewMessageEvent(OppoMessageEvent evt) {
366 logger.debug("onNewMessageEvent: key {} = {}", evt.getKey(), evt.getValue());
367 lastEventReceived = System.currentTimeMillis();
369 String key = evt.getKey();
370 String updateData = evt.getValue().trim();
371 if (this.getThing().getStatus() == ThingStatus.OFFLINE) {
372 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.versionString);
375 synchronized (sequenceLock) {
381 // Player sent a time code update ie: 000 000 T 00:00:01
382 // g1 = title(movie only; cd always 000), g2 = chapter(movie)/track(cd), g3 = time display code,
384 Matcher matcher = TIME_CODE_PATTERN.matcher(updateData);
385 if (matcher.find()) {
386 // only update these when chapter/track changes to prevent spamming the channels with
387 // unnecessary updates
388 if (!currentChapter.equals(matcher.group(2))) {
389 currentChapter = matcher.group(2);
390 // for CDs this will get track 1/x also
391 connector.sendCommand(OppoCommand.QUERY_TITLE_TRACK);
392 // for movies shows chapter 1/x; always 0/0 for CDs
393 connector.sendCommand(OppoCommand.QUERY_CHAPTER);
396 if (!currentTimeMode.equals(matcher.group(3))) {
397 currentTimeMode = matcher.group(3);
398 updateChannelState(CHANNEL_TIME_MODE, currentTimeMode);
400 updateChannelState(CHANNEL_TIME_DISPLAY, matcher.group(4));
402 logger.debug("no match on message: {}", updateData);
409 // these are used with verbose mode 2
410 updateChannelState(CHANNEL_TIME_DISPLAY, updateData);
413 this.versionString = updateData;
416 updateChannelState(CHANNEL_POWER, updateData);
417 if (OFF.equals(updateData)) {
418 currentPlayMode = BLANK;
425 updateChannelState(CHANNEL_POWER, ONE.equals(updateData) ? ON : OFF);
426 if (ZERO.equals(updateData)) {
427 currentPlayMode = BLANK;
437 if (MUTE.equals(updateData) || MUT.equals(updateData)) { // query sends MUTE, update sends MUT
438 updateChannelState(CHANNEL_MUTE, ON);
439 } else if (UMT.equals(updateData)) {
440 updateChannelState(CHANNEL_MUTE, OFF);
442 updateChannelState(CHANNEL_VOLUME, updateData);
443 updateChannelState(CHANNEL_MUTE, OFF);
448 // example: 0 BD-PLAYER, split off just the number
449 updateChannelState(CHANNEL_SOURCE, updateData.split(SPACE)[0]);
452 // we got the playback status update, throw it away and call the query because the text output
454 connector.sendCommand(OppoCommand.QUERY_PLAYBACK_STATUS);
457 // example: 02/10, split off both numbers
458 String[] track = updateData.split(SLASH);
459 if (track.length == 2) {
460 updateChannelState(CHANNEL_CURRENT_TITLE, track[0]);
461 updateChannelState(CHANNEL_TOTAL_TITLE, track[1]);
465 // example: 03/03, split off the both numbers
466 String[] chapter = updateData.split(SLASH);
467 if (chapter.length == 2) {
468 updateChannelState(CHANNEL_CURRENT_CHAPTER, chapter[0]);
469 updateChannelState(CHANNEL_TOTAL_CHAPTER, chapter[1]);
473 // if playback has stopped, we have to zero out Time, Title and Track info and so on manually
474 if (NO_DISC.equals(updateData) || LOADING.equals(updateData) || OPEN.equals(updateData)
475 || CLOSE.equals(updateData) || STOP.equals(updateData)) {
476 updateChannelState(CHANNEL_CURRENT_TITLE, ZERO);
477 updateChannelState(CHANNEL_TOTAL_TITLE, ZERO);
478 updateChannelState(CHANNEL_CURRENT_CHAPTER, ZERO);
479 updateChannelState(CHANNEL_TOTAL_CHAPTER, ZERO);
480 updateChannelState(CHANNEL_TIME_DISPLAY, UNDEF);
481 updateChannelState(CHANNEL_AUDIO_TYPE, UNDEF);
482 updateChannelState(CHANNEL_SUBTITLE_TYPE, UNDEF);
484 updateChannelState(CHANNEL_PLAY_MODE, updateData);
486 // if switching to play mode and not a CD then query the subtitle type...
487 // because if subtitles were on when playback stopped, they got nulled out above
488 // and the subtitle update message ("UST") is not sent when play starts like it is for audio
489 if (PLAY.equals(updateData) && !CDDA.equals(currentDiscType)) {
490 connector.sendCommand(OppoCommand.QUERY_SUBTITLE_TYPE);
492 currentPlayMode = updateData;
495 updateChannelState(CHANNEL_REPEAT_MODE, updateData);
498 updateChannelState(CHANNEL_ZOOM_MODE, updateData);
501 // we got the disc type status update, throw it away
502 // and call the query because the text output is better
503 connector.sendCommand(OppoCommand.QUERY_DISC_TYPE);
505 currentDiscType = updateData;
506 updateChannelState(CHANNEL_DISC_TYPE, updateData);
509 // we got the audio type status update, throw it away
510 // and call the query because the text output is better
511 connector.sendCommand(OppoCommand.QUERY_AUDIO_TYPE);
514 updateChannelState(CHANNEL_AUDIO_TYPE, updateData);
517 // we got the subtitle type status update, throw it away
518 // and call the query because the text output is better
519 connector.sendCommand(OppoCommand.QUERY_SUBTITLE_TYPE);
522 updateChannelState(CHANNEL_SUBTITLE_TYPE, updateData);
524 case UAR: // 203 & 205 only
525 updateChannelState(CHANNEL_ASPECT_RATIO, updateData);
528 // example: _480I60 1080P60 - 1st source res, 2nd output res
529 String[] resolution = updateData.replace(UNDERSCORE, BLANK).split(SPACE);
530 if (resolution.length == 2) {
531 updateChannelState(CHANNEL_SOURCE_RESOLUTION, resolution[0]);
532 updateChannelState(CHANNEL_OUTPUT_RESOLUTION, resolution[1]);
536 updateChannelState(CHANNEL_3D_INDICATOR, updateData);
539 updateChannelState(CHANNEL_SUB_SHIFT, updateData);
542 updateChannelState(CHANNEL_OSD_POSITION, updateData);
546 updateChannelState(CHANNEL_HDMI_MODE, updateData);
548 handleHdmiModeUpdate(updateData);
551 case QHR: // 203 & 205 only
552 updateChannelState(CHANNEL_HDR_MODE, updateData);
555 logger.debug("onNewMessageEvent: unhandled key {}, value: {}", key, updateData);
558 } catch (OppoException e) {
559 logger.debug("Exception processing event from player: {}", e.getMessage());
565 * Schedule the reconnection job
567 private void scheduleReconnectJob() {
568 logger.debug("Schedule reconnect job");
569 cancelReconnectJob();
571 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
572 if (!connector.isConnected()) {
573 logger.debug("Trying to reconnect...");
576 synchronized (sequenceLock) {
577 if (openConnection()) {
579 long prevUpdateTime = lastEventReceived;
581 connector.sendCommand(OppoCommand.SET_VERBOSE_MODE, this.verboseMode);
582 Thread.sleep(SLEEP_BETWEEN_CMD_MS);
584 // if the player is off most of these won't really do much...
585 OppoCommand.INITIAL_COMMANDS.forEach(cmd -> {
587 connector.sendCommand(cmd);
588 Thread.sleep(SLEEP_BETWEEN_CMD_MS);
589 } catch (OppoException | InterruptedException e) {
590 logger.debug("Exception sending initial commands: {}", e.getMessage());
594 // prevUpdateTime should have changed if a message was received from the player
595 if (prevUpdateTime == lastEventReceived) {
596 error = "Player not responding to status requests";
598 } catch (OppoException | InterruptedException e) {
599 error = "First command after connection failed";
600 logger.debug("{}: {}", error, e.getMessage());
603 error = "Reconnection failed";
606 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
609 updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.versionString);
613 }, 1, RECON_POLLING_INTERVAL_SEC, TimeUnit.SECONDS);
617 * Cancel the reconnection job
619 private void cancelReconnectJob() {
620 ScheduledFuture<?> reconnectJob = this.reconnectJob;
621 if (reconnectJob != null) {
622 reconnectJob.cancel(true);
623 this.reconnectJob = null;
628 * Schedule the polling job
630 private void schedulePollingJob() {
631 logger.debug("Schedule polling job");
634 // when the Oppo is off, this will keep the connection (esp Serial over IP) alive and
635 // detect if the connection goes down
636 pollingJob = scheduler.scheduleWithFixedDelay(() -> {
637 if (connector.isConnected()) {
638 logger.debug("Polling the player for updated status...");
640 synchronized (sequenceLock) {
642 // if using direct IP connection on the 83/9x/10x, no unsolicited updates are sent
643 // so we must query everything to know what changed.
645 connector.sendCommand(OppoCommand.QUERY_POWER_STATUS);
647 OppoCommand.QUERY_COMMANDS.forEach(cmd -> {
649 connector.sendCommand(cmd);
650 Thread.sleep(SLEEP_BETWEEN_CMD_MS);
651 } catch (OppoException | InterruptedException e) {
652 logger.debug("Exception sending polling commands: {}", e.getMessage());
658 // for Verbose mode 2 get the current play back time if we are playing, otherwise just do NO_OP
659 if ((VERBOSE_2.equals(this.verboseMode) && PLAY.equals(currentPlayMode))
660 || (isBdpIP && isPowerOn)) {
661 switch (currentTimeMode) {
663 connector.sendCommand(OppoCommand.QUERY_TITLE_ELAPSED);
666 connector.sendCommand(OppoCommand.QUERY_TITLE_REMAIN);
669 connector.sendCommand(OppoCommand.QUERY_CHAPTER_ELAPSED);
672 connector.sendCommand(OppoCommand.QUERY_CHAPTER_REMAIN);
675 Thread.sleep(SLEEP_BETWEEN_CMD_MS);
677 // make queries to refresh total number of titles/tracks & chapters
678 connector.sendCommand(OppoCommand.QUERY_TITLE_TRACK);
679 Thread.sleep(SLEEP_BETWEEN_CMD_MS);
680 connector.sendCommand(OppoCommand.QUERY_CHAPTER);
681 } else if (!isBdpIP) {
683 connector.sendCommand(OppoCommand.NO_OP);
686 } catch (OppoException | InterruptedException e) {
687 logger.warn("Polling error: {}", e.getMessage());
690 // if the last event received was more than 1.25 intervals ago,
691 // the player is not responding even though the connection is still good
692 if ((System.currentTimeMillis() - lastEventReceived) > (POLLING_INTERVAL_SEC * 1.25 * 1000)) {
693 logger.debug("Player not responding to status requests");
694 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
695 "Player not responding to status requests");
697 scheduleReconnectJob();
701 }, INITIAL_POLLING_DELAY_SEC, POLLING_INTERVAL_SEC, TimeUnit.SECONDS);
705 * Cancel the polling job
707 private void cancelPollingJob() {
708 ScheduledFuture<?> pollingJob = this.pollingJob;
709 if (pollingJob != null) {
710 pollingJob.cancel(true);
711 this.pollingJob = null;
716 * Update the state of a channel
718 * @param channel the channel
719 * @param value the value to be updated
721 private void updateChannelState(String channel, String value) {
722 if (!isLinked(channel)) {
726 if (UNDEF.equals(value)) {
727 updateState(channel, UnDefType.UNDEF);
731 State state = UnDefType.UNDEF;
734 case CHANNEL_TIME_DISPLAY:
735 String[] timeArr = value.split(COLON);
736 if (timeArr.length == 3) {
737 int seconds = (Integer.parseInt(timeArr[0]) * 3600) + (Integer.parseInt(timeArr[1]) * 60)
738 + Integer.parseInt(timeArr[2]);
739 state = new QuantityType<>(seconds, SmartHomeUnits.SECOND);
741 state = UnDefType.UNDEF;
746 state = ON.equals(value) ? OnOffType.ON : OnOffType.OFF;
749 case CHANNEL_SUB_SHIFT:
750 case CHANNEL_OSD_POSITION:
751 case CHANNEL_CURRENT_TITLE:
752 case CHANNEL_TOTAL_TITLE:
753 case CHANNEL_CURRENT_CHAPTER:
754 case CHANNEL_TOTAL_CHAPTER:
755 state = new DecimalType(value);
758 state = new PercentType(BigDecimal.valueOf(Integer.parseInt(value)));
760 case CHANNEL_PLAY_MODE:
761 case CHANNEL_TIME_MODE:
762 case CHANNEL_REPEAT_MODE:
763 case CHANNEL_ZOOM_MODE:
764 case CHANNEL_DISC_TYPE:
765 case CHANNEL_AUDIO_TYPE:
766 case CHANNEL_SUBTITLE_TYPE:
767 case CHANNEL_ASPECT_RATIO:
768 case CHANNEL_SOURCE_RESOLUTION:
769 case CHANNEL_OUTPUT_RESOLUTION:
770 case CHANNEL_3D_INDICATOR:
771 case CHANNEL_HDMI_MODE:
772 case CHANNEL_HDR_MODE:
773 state = new StringType(value);
778 updateState(channel, state);
782 * Handle a button press from a UI Player item
784 * @param command the control button press command received
786 private void handleControlCommand(Command command) throws OppoException {
787 if (command instanceof PlayPauseType) {
788 if (command == PlayPauseType.PLAY) {
789 connector.sendCommand(OppoCommand.PLAY);
790 } else if (command == PlayPauseType.PAUSE) {
791 connector.sendCommand(OppoCommand.PAUSE);
793 } else if (command instanceof NextPreviousType) {
794 if (command == NextPreviousType.NEXT) {
795 connector.sendCommand(OppoCommand.NEXT);
796 } else if (command == NextPreviousType.PREVIOUS) {
797 connector.sendCommand(OppoCommand.PREV);
799 } else if (command instanceof RewindFastforwardType) {
800 if (command == RewindFastforwardType.FASTFORWARD) {
801 connector.sendCommand(OppoCommand.FFORWARD);
802 } else if (command == RewindFastforwardType.REWIND) {
803 connector.sendCommand(OppoCommand.REWIND);
806 logger.warn("Unknown control command: {}", command);
810 private void buildOptionDropdowns(int model) {
811 if (model == MODEL83 || model == MODEL103 || model == MODEL105) {
812 hdmiModeOptions.add(new StateOption("AUTO", "Auto"));
813 hdmiModeOptions.add(new StateOption("SRC", "Source Direct"));
814 if (!(model == MODEL83)) {
815 hdmiModeOptions.add(new StateOption("4K2K", "4K*2K"));
817 hdmiModeOptions.add(new StateOption("1080P", "1080P"));
818 hdmiModeOptions.add(new StateOption("1080I", "1080I"));
819 hdmiModeOptions.add(new StateOption("720P", "720P"));
820 hdmiModeOptions.add(new StateOption("SDP", "480P"));
821 hdmiModeOptions.add(new StateOption("SDI", "480I"));
824 if (model == MODEL103 || model == MODEL105) {
825 inputSourceOptions.add(new StateOption("0", "Blu-Ray Player"));
826 inputSourceOptions.add(new StateOption("1", "HDMI/MHL IN-Front"));
827 inputSourceOptions.add(new StateOption("2", "HDMI IN-Back"));
828 inputSourceOptions.add(new StateOption("3", "ARC"));
830 if (model == MODEL105) {
831 inputSourceOptions.add(new StateOption("4", "Optical In"));
832 inputSourceOptions.add(new StateOption("5", "Coaxial In"));
833 inputSourceOptions.add(new StateOption("6", "USB Audio In"));
837 if (model == MODEL203 || model == MODEL205) {
838 hdmiModeOptions.add(new StateOption("AUTO", "Auto"));
839 hdmiModeOptions.add(new StateOption("SRC", "Source Direct"));
840 hdmiModeOptions.add(new StateOption("UHD_AUTO", "UHD Auto"));
841 hdmiModeOptions.add(new StateOption("UHD24", "UHD24"));
842 hdmiModeOptions.add(new StateOption("UHD50", "UHD50"));
843 hdmiModeOptions.add(new StateOption("UHD60", "UHD60"));
844 hdmiModeOptions.add(new StateOption("1080P_AUTO", "1080P Auto"));
845 hdmiModeOptions.add(new StateOption("1080P24", "1080P24"));
846 hdmiModeOptions.add(new StateOption("1080P50", "1080P50"));
847 hdmiModeOptions.add(new StateOption("1080P60", "1080P60"));
848 hdmiModeOptions.add(new StateOption("1080I50", "1080I50"));
849 hdmiModeOptions.add(new StateOption("1080I60", "1080I60"));
850 hdmiModeOptions.add(new StateOption("720P50", "720P50"));
851 hdmiModeOptions.add(new StateOption("720P60", "720P60"));
852 hdmiModeOptions.add(new StateOption("576P", "567P"));
853 hdmiModeOptions.add(new StateOption("576I", "567I"));
854 hdmiModeOptions.add(new StateOption("480P", "480P"));
855 hdmiModeOptions.add(new StateOption("480I", "480I"));
857 inputSourceOptions.add(new StateOption("0", "Blu-Ray Player"));
858 inputSourceOptions.add(new StateOption("1", "HDMI IN"));
859 inputSourceOptions.add(new StateOption("2", "ARC"));
861 if (model == MODEL205) {
862 inputSourceOptions.add(new StateOption("3", "Optical In"));
863 inputSourceOptions.add(new StateOption("4", "Coaxial In"));
864 inputSourceOptions.add(new StateOption("5", "USB Audio In"));
869 private void handleHdmiModeUpdate(String updateData) {
870 // ugly... a couple of the query hdmi mode response codes on the earlier models don't match the code to set it
871 // some of this protocol is weird like that...
872 if ("480I".equals(updateData)) {
873 updateChannelState(CHANNEL_HDMI_MODE, "SDI");
874 } else if ("480P".equals(updateData)) {
875 updateChannelState(CHANNEL_HDMI_MODE, "SDP");
876 } else if ("4K*2K".equals(updateData)) {
877 updateChannelState(CHANNEL_HDMI_MODE, "4K2K");
879 updateChannelState(CHANNEL_HDMI_MODE, updateData);