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.pioneeravr.internal.handler;
15 import java.util.concurrent.ScheduledFuture;
16 import java.util.concurrent.TimeUnit;
17 import java.util.regex.Matcher;
19 import org.openhab.binding.pioneeravr.internal.PioneerAvrBindingConstants;
20 import org.openhab.binding.pioneeravr.internal.protocol.RequestResponseFactory;
21 import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnection;
22 import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnectionException;
23 import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrResponse;
24 import org.openhab.binding.pioneeravr.internal.protocol.avr.CommandTypeNotSupportedException;
25 import org.openhab.binding.pioneeravr.internal.protocol.event.AvrDisconnectionEvent;
26 import org.openhab.binding.pioneeravr.internal.protocol.event.AvrDisconnectionListener;
27 import org.openhab.binding.pioneeravr.internal.protocol.event.AvrStatusUpdateEvent;
28 import org.openhab.binding.pioneeravr.internal.protocol.event.AvrUpdateListener;
29 import org.openhab.binding.pioneeravr.internal.protocol.states.MuteStateValues;
30 import org.openhab.binding.pioneeravr.internal.protocol.states.PowerStateValues;
31 import org.openhab.binding.pioneeravr.internal.protocol.utils.DisplayInformationConverter;
32 import org.openhab.binding.pioneeravr.internal.protocol.utils.VolumeConverter;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.PercentType;
36 import org.openhab.core.library.types.StringType;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.openhab.core.types.UnDefType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * The {@link AbstractAvrHandler} is responsible for handling commands, which are sent to one of the channels through an
51 * @author Antoine Besnard - Initial contribution
52 * @author Leroy Foerster - Listening Mode, Playing Listening Mode
54 public abstract class AbstractAvrHandler extends BaseThingHandler
55 implements AvrUpdateListener, AvrDisconnectionListener {
57 private final Logger logger = LoggerFactory.getLogger(AbstractAvrHandler.class);
59 private AvrConnection connection;
60 private ScheduledFuture<?> statusCheckerFuture;
62 public AbstractAvrHandler(Thing thing) {
64 this.connection = createConnection();
66 this.connection.addUpdateListener(this);
67 this.connection.addDisconnectionListener(this);
71 * Create a new connection to the AVR.
75 protected abstract AvrConnection createConnection();
78 * Initialize the state of the AVR.
81 public void initialize() {
82 logger.debug("Initializing handler for Pioneer AVR @{}", connection.getConnectionName());
83 updateStatus(ThingStatus.ONLINE);
85 // Start the status checker
86 Runnable statusChecker = () -> {
88 logger.debug("Checking status of AVR @{}", connection.getConnectionName());
90 } catch (LinkageError e) {
92 "Failed to check the status for AVR @{}. If a Serial link is used to connect to the AVR, please check that the Bundle org.openhab.io.transport.serial is available. Cause: {}",
93 connection.getConnectionName(), e.getMessage());
94 // Stop to check the status of this AVR.
95 if (statusCheckerFuture != null) {
96 statusCheckerFuture.cancel(false);
100 statusCheckerFuture = scheduler.scheduleWithFixedDelay(statusChecker, 1, 10, TimeUnit.SECONDS);
104 * Close the connection and stop the status checker.
107 public void dispose() {
109 if (statusCheckerFuture != null) {
110 statusCheckerFuture.cancel(true);
112 if (connection != null) {
118 * Called when a Power ON state update is received from the AVR for the given zone.
120 public void onPowerOn(int zone) {
121 // When the AVR is Powered ON, query the volume, the mute state and the source input of the zone
122 connection.sendVolumeQuery(zone);
123 connection.sendMuteQuery(zone);
124 connection.sendInputSourceQuery(zone);
125 connection.sendListeningModeQuery(zone);
129 * Called when a Power OFF state update is received from the AVR.
131 public void onPowerOff(int zone) {
132 // When the AVR is Powered OFF, update the status of channels to Undefined
133 updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, zone), UnDefType.UNDEF);
134 updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL, zone), UnDefType.UNDEF);
135 updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL, zone), UnDefType.UNDEF);
136 updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, zone), UnDefType.UNDEF);
137 updateState(getChannelUID(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL, zone), UnDefType.UNDEF);
138 updateState(getChannelUID(PioneerAvrBindingConstants.PLAYING_LISTENING_MODE_CHANNEL, zone), UnDefType.UNDEF);
142 * Check the status of the AVR. Return true if the AVR is online, else return false.
146 private void checkStatus() {
147 // If the power query request has not been sent, the connection to the
148 // AVR has failed. So update its status to OFFLINE.
149 if (!connection.sendPowerQuery(1)) {
150 updateStatus(ThingStatus.OFFLINE);
152 // If the power query has succeeded, the AVR status is ONLINE.
153 updateStatus(ThingStatus.ONLINE);
154 // Then send a power query for zone 2 and 3
155 connection.sendPowerQuery(2);
156 connection.sendPowerQuery(3);
161 * Send a command to the AVR based on the openHAB command received.
164 public void handleCommand(ChannelUID channelUID, Command command) {
166 boolean commandSent = false;
167 boolean unknownCommand = false;
169 if (channelUID.getId().contains(PioneerAvrBindingConstants.POWER_CHANNEL)) {
170 if (command == RefreshType.REFRESH) {
171 commandSent = connection.sendPowerQuery(getZoneFromChannelUID(channelUID.getId()));
173 commandSent = connection.sendPowerCommand(command, getZoneFromChannelUID(channelUID.getId()));
175 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL)
176 || channelUID.getId().contains(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL)) {
177 if (command == RefreshType.REFRESH) {
178 commandSent = connection.sendVolumeQuery(getZoneFromChannelUID(channelUID.getId()));
180 commandSent = connection.sendVolumeCommand(command, getZoneFromChannelUID(channelUID.getId()));
182 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL)) {
183 if (command == RefreshType.REFRESH) {
184 commandSent = connection.sendInputSourceQuery(getZoneFromChannelUID(channelUID.getId()));
186 commandSent = connection.sendInputSourceCommand(command, getZoneFromChannelUID(channelUID.getId()));
188 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL)) {
189 if (command == RefreshType.REFRESH) {
190 commandSent = connection.sendListeningModeQuery(getZoneFromChannelUID(channelUID.getId()));
192 commandSent = connection.sendListeningModeCommand(command,
193 getZoneFromChannelUID(channelUID.getId()));
195 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.MUTE_CHANNEL)) {
196 if (command == RefreshType.REFRESH) {
197 commandSent = connection.sendMuteQuery(getZoneFromChannelUID(channelUID.getId()));
199 commandSent = connection.sendMuteCommand(command, getZoneFromChannelUID(channelUID.getId()));
202 unknownCommand = true;
205 // If the command is not unknown and has not been sent, the AVR is Offline
206 if (!commandSent && !unknownCommand) {
209 } catch (CommandTypeNotSupportedException e) {
210 logger.warn("Unsupported command type received for channel {}.", channelUID.getId());
215 * Called when a status update is received from the AVR.
218 public void statusUpdateReceived(AvrStatusUpdateEvent event) {
220 AvrResponse response = RequestResponseFactory.getIpControlResponse(event.getData());
222 switch (response.getResponseType()) {
224 managePowerStateUpdate(response);
228 manageVolumeLevelUpdate(response);
232 manageMuteStateUpdate(response);
235 case INPUT_SOURCE_CHANNEL:
236 manageInputSourceChannelUpdate(response);
240 manageListeningModeUpdate(response);
243 case PLAYING_LISTENING_MODE:
244 managePlayingListeningModeUpdate(response);
247 case DISPLAY_INFORMATION:
248 manageDisplayedInformationUpdate(response);
252 logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
253 event.getConnection());
255 } catch (AvrConnectionException e) {
256 logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
257 event.getConnection());
262 * Called when the AVR is disconnected
265 public void onDisconnection(AvrDisconnectionEvent event) {
270 * Process the AVR disconnection.
272 private void onDisconnection() {
273 updateStatus(ThingStatus.OFFLINE);
277 * Notify an AVR power state update to openHAB
281 private void managePowerStateUpdate(AvrResponse response) {
282 OnOffType state = PowerStateValues.ON_VALUE.equals(response.getParameterValue()) ? OnOffType.ON : OnOffType.OFF;
284 // When a Power ON state update is received, call the onPowerOn method.
285 if (OnOffType.ON == state) {
286 onPowerOn(response.getZone());
288 onPowerOff(response.getZone());
291 updateState(getChannelUID(PioneerAvrBindingConstants.POWER_CHANNEL, response.getZone()), state);
295 * Notify an AVR volume level update to openHAB
299 private void manageVolumeLevelUpdate(AvrResponse response) {
300 updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL, response.getZone()), new DecimalType(
301 VolumeConverter.convertFromIpControlVolumeToDb(response.getParameterValue(), response.getZone())));
302 updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL, response.getZone()),
303 new PercentType((int) VolumeConverter.convertFromIpControlVolumeToPercent(response.getParameterValue(),
304 response.getZone())));
308 * Notify an AVR mute state update to openHAB
312 private void manageMuteStateUpdate(AvrResponse response) {
313 updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, response.getZone()),
314 response.getParameterValue().equals(MuteStateValues.OFF_VALUE) ? OnOffType.OFF : OnOffType.ON);
318 * Notify an AVR input source channel update to openHAB
322 private void manageInputSourceChannelUpdate(AvrResponse response) {
323 updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, response.getZone()),
324 new StringType(response.getParameterValue()));
328 * Notify an AVR now-playing, in-effect listening mode (audio output format) update to openHAB
332 private void managePlayingListeningModeUpdate(AvrResponse response) {
333 updateState(getChannelUID(PioneerAvrBindingConstants.PLAYING_LISTENING_MODE_CHANNEL, response.getZone()),
334 new StringType(response.getParameterValue()));
338 * Notify an AVR set listening mode (user-selected audio mode) update to openHAB
342 private void manageListeningModeUpdate(AvrResponse response) {
343 updateState(getChannelUID(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL, response.getZone()),
344 new StringType(response.getParameterValue()));
348 * Notify an AVR displayed information update to openHAB
352 private void manageDisplayedInformationUpdate(AvrResponse response) {
353 updateState(PioneerAvrBindingConstants.DISPLAY_INFORMATION_CHANNEL,
354 new StringType(DisplayInformationConverter.convertMessageFromIpControl(response.getParameterValue())));
358 * Build the channelUID from the channel name and the zone number.
364 protected String getChannelUID(String channelName, int zone) {
365 return String.format(PioneerAvrBindingConstants.GROUP_CHANNEL_PATTERN, zone, channelName);
369 * Return the zone from the given channelUID.
371 * Return 0 if the zone cannot be extracted from the channelUID.
376 protected int getZoneFromChannelUID(String channelUID) {
378 Matcher matcher = PioneerAvrBindingConstants.GROUP_CHANNEL_ZONE_PATTERN.matcher(channelUID);
379 if (matcher.find()) {
380 zone = Integer.valueOf(matcher.group(1));