2 * Copyright (c) 2010-2023 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);
127 // Channels which are not bound to any specific zone
128 connection.sendMCACCMemoryQuery();
132 * Called when a Power OFF state update is received from the AVR.
134 public void onPowerOff(int zone) {
135 // When the AVR is Powered OFF, update the status of channels to Undefined
136 updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, zone), UnDefType.UNDEF);
137 updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL, zone), UnDefType.UNDEF);
138 updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL, zone), UnDefType.UNDEF);
139 updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, zone), UnDefType.UNDEF);
140 updateState(getChannelUID(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL, zone), UnDefType.UNDEF);
141 updateState(getChannelUID(PioneerAvrBindingConstants.PLAYING_LISTENING_MODE_CHANNEL, zone), UnDefType.UNDEF);
143 // Channels which are not bound to any specific zone
145 updateState(PioneerAvrBindingConstants.MCACC_MEMORY_CHANNEL, UnDefType.UNDEF);
150 * Check the status of the AVR. Return true if the AVR is online, else return false.
154 private void checkStatus() {
155 // If the power query request has not been sent, the connection to the
156 // AVR has failed. So update its status to OFFLINE.
157 if (!connection.sendPowerQuery(1)) {
158 updateStatus(ThingStatus.OFFLINE);
160 // If the power query has succeeded, the AVR status is ONLINE.
161 updateStatus(ThingStatus.ONLINE);
162 // Then send a power query for zone 2 and 3
163 connection.sendPowerQuery(2);
164 connection.sendPowerQuery(3);
169 * Send a command to the AVR based on the openHAB command received.
172 public void handleCommand(ChannelUID channelUID, Command command) {
174 boolean commandSent = false;
175 boolean unknownCommand = false;
177 if (channelUID.getId().contains(PioneerAvrBindingConstants.POWER_CHANNEL)) {
178 if (command == RefreshType.REFRESH) {
179 commandSent = connection.sendPowerQuery(getZoneFromChannelUID(channelUID.getId()));
181 commandSent = connection.sendPowerCommand(command, getZoneFromChannelUID(channelUID.getId()));
183 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL)
184 || channelUID.getId().contains(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL)) {
185 if (command == RefreshType.REFRESH) {
186 commandSent = connection.sendVolumeQuery(getZoneFromChannelUID(channelUID.getId()));
188 commandSent = connection.sendVolumeCommand(command, getZoneFromChannelUID(channelUID.getId()));
190 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL)) {
191 if (command == RefreshType.REFRESH) {
192 commandSent = connection.sendInputSourceQuery(getZoneFromChannelUID(channelUID.getId()));
194 commandSent = connection.sendInputSourceCommand(command, getZoneFromChannelUID(channelUID.getId()));
196 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL)) {
197 if (command == RefreshType.REFRESH) {
198 commandSent = connection.sendListeningModeQuery(getZoneFromChannelUID(channelUID.getId()));
200 commandSent = connection.sendListeningModeCommand(command,
201 getZoneFromChannelUID(channelUID.getId()));
203 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.MUTE_CHANNEL)) {
204 if (command == RefreshType.REFRESH) {
205 commandSent = connection.sendMuteQuery(getZoneFromChannelUID(channelUID.getId()));
207 commandSent = connection.sendMuteCommand(command, getZoneFromChannelUID(channelUID.getId()));
209 } else if (channelUID.getId().contains(PioneerAvrBindingConstants.MCACC_MEMORY_CHANNEL)) {
210 if (command == RefreshType.REFRESH) {
211 commandSent = connection.sendMCACCMemoryQuery();
213 commandSent = connection.sendMCACCMemoryCommand(command);
216 unknownCommand = true;
219 // If the command is not unknown and has not been sent, the AVR is Offline
220 if (!commandSent && !unknownCommand) {
223 } catch (CommandTypeNotSupportedException e) {
224 logger.warn("Unsupported command type received for channel {}.", channelUID.getId());
229 * Called when a status update is received from the AVR.
232 public void statusUpdateReceived(AvrStatusUpdateEvent event) {
234 AvrResponse response = RequestResponseFactory.getIpControlResponse(event.getData());
236 switch (response.getResponseType()) {
238 managePowerStateUpdate(response);
242 manageVolumeLevelUpdate(response);
246 manageMuteStateUpdate(response);
249 case INPUT_SOURCE_CHANNEL:
250 manageInputSourceChannelUpdate(response);
254 manageListeningModeUpdate(response);
257 case PLAYING_LISTENING_MODE:
258 managePlayingListeningModeUpdate(response);
261 case DISPLAY_INFORMATION:
262 manageDisplayedInformationUpdate(response);
266 manageMCACCMemoryUpdate(response);
270 logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
271 event.getConnection());
273 } catch (AvrConnectionException e) {
274 logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
275 event.getConnection());
280 * Called when the AVR is disconnected
283 public void onDisconnection(AvrDisconnectionEvent event) {
288 * Process the AVR disconnection.
290 private void onDisconnection() {
291 updateStatus(ThingStatus.OFFLINE);
295 * Notify an AVR power state update to openHAB
299 private void managePowerStateUpdate(AvrResponse response) {
300 OnOffType state = PowerStateValues.ON_VALUE.equals(response.getParameterValue()) ? OnOffType.ON : OnOffType.OFF;
302 // When a Power ON state update is received, call the onPowerOn method.
303 if (OnOffType.ON == state) {
304 onPowerOn(response.getZone());
306 onPowerOff(response.getZone());
309 updateState(getChannelUID(PioneerAvrBindingConstants.POWER_CHANNEL, response.getZone()), state);
313 * Notify an AVR volume level update to openHAB
317 private void manageVolumeLevelUpdate(AvrResponse response) {
318 updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL, response.getZone()), new DecimalType(
319 VolumeConverter.convertFromIpControlVolumeToDb(response.getParameterValue(), response.getZone())));
320 updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL, response.getZone()),
321 new PercentType((int) VolumeConverter.convertFromIpControlVolumeToPercent(response.getParameterValue(),
322 response.getZone())));
326 * Notify an AVR mute state update to openHAB
330 private void manageMuteStateUpdate(AvrResponse response) {
331 updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, response.getZone()),
332 response.getParameterValue().equals(MuteStateValues.OFF_VALUE) ? OnOffType.OFF : OnOffType.ON);
336 * Notify an AVR input source channel update to openHAB
340 private void manageInputSourceChannelUpdate(AvrResponse response) {
341 updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, response.getZone()),
342 new StringType(response.getParameterValue()));
346 * Notify an AVR now-playing, in-effect listening mode (audio output format) update to openHAB
350 private void managePlayingListeningModeUpdate(AvrResponse response) {
351 updateState(getChannelUID(PioneerAvrBindingConstants.PLAYING_LISTENING_MODE_CHANNEL, response.getZone()),
352 new StringType(response.getParameterValue()));
356 * Notify an AVR set listening mode (user-selected audio mode) update to openHAB
360 private void manageListeningModeUpdate(AvrResponse response) {
361 updateState(getChannelUID(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL, response.getZone()),
362 new StringType(response.getParameterValue()));
366 * Notify an AVR displayed information update to openHAB
370 private void manageDisplayedInformationUpdate(AvrResponse response) {
371 updateState(PioneerAvrBindingConstants.DISPLAY_INFORMATION_CHANNEL,
372 new StringType(DisplayInformationConverter.convertMessageFromIpControl(response.getParameterValue())));
376 * Notify an AVR MCACC Memory update to openHAB
380 private void manageMCACCMemoryUpdate(AvrResponse response) {
381 updateState(PioneerAvrBindingConstants.MCACC_MEMORY_CHANNEL, new StringType(response.getParameterValue()));
385 * Build the channelUID from the channel name and the zone number.
391 protected String getChannelUID(String channelName, int zone) {
392 return String.format(PioneerAvrBindingConstants.GROUP_CHANNEL_PATTERN, zone, channelName);
396 * Return the zone from the given channelUID.
398 * Return 0 if the zone cannot be extracted from the channelUID.
403 protected int getZoneFromChannelUID(String channelUID) {
405 Matcher matcher = PioneerAvrBindingConstants.GROUP_CHANNEL_ZONE_PATTERN.matcher(channelUID);
406 if (matcher.find()) {
407 zone = Integer.valueOf(matcher.group(1));