]> git.basschouten.com Git - openhab-addons.git/blob
cd950ace3777fe3e37e1dc12afb1628a6e525850
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.pioneeravr.internal.handler;
14
15 import java.util.concurrent.ScheduledFuture;
16 import java.util.concurrent.TimeUnit;
17 import java.util.regex.Matcher;
18
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;
46
47 /**
48  * The {@link AbstractAvrHandler} is responsible for handling commands, which are sent to one of the channels through an
49  * AVR connection.
50  *
51  * @author Antoine Besnard - Initial contribution
52  * @author Leroy Foerster - Listening Mode, Playing Listening Mode
53  */
54 public abstract class AbstractAvrHandler extends BaseThingHandler
55         implements AvrUpdateListener, AvrDisconnectionListener {
56
57     private final Logger logger = LoggerFactory.getLogger(AbstractAvrHandler.class);
58
59     private AvrConnection connection;
60     private ScheduledFuture<?> statusCheckerFuture;
61
62     public AbstractAvrHandler(Thing thing) {
63         super(thing);
64         this.connection = createConnection();
65
66         this.connection.addUpdateListener(this);
67         this.connection.addDisconnectionListener(this);
68     }
69
70     /**
71      * Create a new connection to the AVR.
72      *
73      * @return
74      */
75     protected abstract AvrConnection createConnection();
76
77     /**
78      * Initialize the state of the AVR.
79      */
80     @Override
81     public void initialize() {
82         logger.debug("Initializing handler for Pioneer AVR @{}", connection.getConnectionName());
83         updateStatus(ThingStatus.ONLINE);
84
85         // Start the status checker
86         Runnable statusChecker = () -> {
87             try {
88                 logger.debug("Checking status of AVR @{}", connection.getConnectionName());
89                 checkStatus();
90             } catch (LinkageError e) {
91                 logger.warn(
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);
97                 }
98             }
99         };
100         statusCheckerFuture = scheduler.scheduleWithFixedDelay(statusChecker, 1, 10, TimeUnit.SECONDS);
101     }
102
103     /**
104      * Close the connection and stop the status checker.
105      */
106     @Override
107     public void dispose() {
108         super.dispose();
109         if (statusCheckerFuture != null) {
110             statusCheckerFuture.cancel(true);
111         }
112         if (connection != null) {
113             connection.close();
114         }
115     }
116
117     /**
118      * Called when a Power ON state update is received from the AVR for the given zone.
119      */
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);
126     }
127
128     /**
129      * Called when a Power OFF state update is received from the AVR.
130      */
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);
139     }
140
141     /**
142      * Check the status of the AVR. Return true if the AVR is online, else return false.
143      *
144      * @return
145      */
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);
151         } else {
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);
157         }
158     }
159
160     /**
161      * Send a command to the AVR based on the openHAB command received.
162      */
163     @Override
164     public void handleCommand(ChannelUID channelUID, Command command) {
165         try {
166             boolean commandSent = false;
167             boolean unknownCommand = false;
168
169             if (channelUID.getId().contains(PioneerAvrBindingConstants.POWER_CHANNEL)) {
170                 if (command == RefreshType.REFRESH) {
171                     commandSent = connection.sendPowerQuery(getZoneFromChannelUID(channelUID.getId()));
172                 } else {
173                     commandSent = connection.sendPowerCommand(command, getZoneFromChannelUID(channelUID.getId()));
174                 }
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()));
179                 } else {
180                     commandSent = connection.sendVolumeCommand(command, getZoneFromChannelUID(channelUID.getId()));
181                 }
182             } else if (channelUID.getId().contains(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL)) {
183                 if (command == RefreshType.REFRESH) {
184                     commandSent = connection.sendInputSourceQuery(getZoneFromChannelUID(channelUID.getId()));
185                 } else {
186                     commandSent = connection.sendInputSourceCommand(command, getZoneFromChannelUID(channelUID.getId()));
187                 }
188             } else if (channelUID.getId().contains(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL)) {
189                 if (command == RefreshType.REFRESH) {
190                     commandSent = connection.sendListeningModeQuery(getZoneFromChannelUID(channelUID.getId()));
191                 } else {
192                     commandSent = connection.sendListeningModeCommand(command,
193                             getZoneFromChannelUID(channelUID.getId()));
194                 }
195             } else if (channelUID.getId().contains(PioneerAvrBindingConstants.MUTE_CHANNEL)) {
196                 if (command == RefreshType.REFRESH) {
197                     commandSent = connection.sendMuteQuery(getZoneFromChannelUID(channelUID.getId()));
198                 } else {
199                     commandSent = connection.sendMuteCommand(command, getZoneFromChannelUID(channelUID.getId()));
200                 }
201             } else {
202                 unknownCommand = true;
203             }
204
205             // If the command is not unknown and has not been sent, the AVR is Offline
206             if (!commandSent && !unknownCommand) {
207                 onDisconnection();
208             }
209         } catch (CommandTypeNotSupportedException e) {
210             logger.warn("Unsupported command type received for channel {}.", channelUID.getId());
211         }
212     }
213
214     /**
215      * Called when a status update is received from the AVR.
216      */
217     @Override
218     public void statusUpdateReceived(AvrStatusUpdateEvent event) {
219         try {
220             AvrResponse response = RequestResponseFactory.getIpControlResponse(event.getData());
221
222             switch (response.getResponseType()) {
223                 case POWER_STATE:
224                     managePowerStateUpdate(response);
225                     break;
226
227                 case VOLUME_LEVEL:
228                     manageVolumeLevelUpdate(response);
229                     break;
230
231                 case MUTE_STATE:
232                     manageMuteStateUpdate(response);
233                     break;
234
235                 case INPUT_SOURCE_CHANNEL:
236                     manageInputSourceChannelUpdate(response);
237                     break;
238
239                 case LISTENING_MODE:
240                     manageListeningModeUpdate(response);
241                     break;
242
243                 case PLAYING_LISTENING_MODE:
244                     managePlayingListeningModeUpdate(response);
245                     break;
246
247                 case DISPLAY_INFORMATION:
248                     manageDisplayedInformationUpdate(response);
249                     break;
250
251                 default:
252                     logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
253                             event.getConnection());
254             }
255         } catch (AvrConnectionException e) {
256             logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
257                     event.getConnection());
258         }
259     }
260
261     /**
262      * Called when the AVR is disconnected
263      */
264     @Override
265     public void onDisconnection(AvrDisconnectionEvent event) {
266         onDisconnection();
267     }
268
269     /**
270      * Process the AVR disconnection.
271      */
272     private void onDisconnection() {
273         updateStatus(ThingStatus.OFFLINE);
274     }
275
276     /**
277      * Notify an AVR power state update to openHAB
278      *
279      * @param response
280      */
281     private void managePowerStateUpdate(AvrResponse response) {
282         OnOffType state = PowerStateValues.ON_VALUE.equals(response.getParameterValue()) ? OnOffType.ON : OnOffType.OFF;
283
284         // When a Power ON state update is received, call the onPowerOn method.
285         if (OnOffType.ON == state) {
286             onPowerOn(response.getZone());
287         } else {
288             onPowerOff(response.getZone());
289         }
290
291         updateState(getChannelUID(PioneerAvrBindingConstants.POWER_CHANNEL, response.getZone()), state);
292     }
293
294     /**
295      * Notify an AVR volume level update to openHAB
296      *
297      * @param response
298      */
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())));
305     }
306
307     /**
308      * Notify an AVR mute state update to openHAB
309      *
310      * @param response
311      */
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);
315     }
316
317     /**
318      * Notify an AVR input source channel update to openHAB
319      *
320      * @param response
321      */
322     private void manageInputSourceChannelUpdate(AvrResponse response) {
323         updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, response.getZone()),
324                 new StringType(response.getParameterValue()));
325     }
326
327     /**
328      * Notify an AVR now-playing, in-effect listening mode (audio output format) update to openHAB
329      *
330      * @param response
331      */
332     private void managePlayingListeningModeUpdate(AvrResponse response) {
333         updateState(getChannelUID(PioneerAvrBindingConstants.PLAYING_LISTENING_MODE_CHANNEL, response.getZone()),
334                 new StringType(response.getParameterValue()));
335     }
336
337     /**
338      * Notify an AVR set listening mode (user-selected audio mode) update to openHAB
339      *
340      * @param response
341      */
342     private void manageListeningModeUpdate(AvrResponse response) {
343         updateState(getChannelUID(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL, response.getZone()),
344                 new StringType(response.getParameterValue()));
345     }
346
347     /**
348      * Notify an AVR displayed information update to openHAB
349      *
350      * @param response
351      */
352     private void manageDisplayedInformationUpdate(AvrResponse response) {
353         updateState(PioneerAvrBindingConstants.DISPLAY_INFORMATION_CHANNEL,
354                 new StringType(DisplayInformationConverter.convertMessageFromIpControl(response.getParameterValue())));
355     }
356
357     /**
358      * Build the channelUID from the channel name and the zone number.
359      *
360      * @param channelName
361      * @param zone
362      * @return
363      */
364     protected String getChannelUID(String channelName, int zone) {
365         return String.format(PioneerAvrBindingConstants.GROUP_CHANNEL_PATTERN, zone, channelName);
366     }
367
368     /**
369      * Return the zone from the given channelUID.
370      *
371      * Return 0 if the zone cannot be extracted from the channelUID.
372      *
373      * @param channelUID
374      * @return
375      */
376     protected int getZoneFromChannelUID(String channelUID) {
377         int zone = 0;
378         Matcher matcher = PioneerAvrBindingConstants.GROUP_CHANNEL_ZONE_PATTERN.matcher(channelUID);
379         if (matcher.find()) {
380             zone = Integer.valueOf(matcher.group(1));
381         }
382         return zone;
383     }
384 }