]> git.basschouten.com Git - openhab-addons.git/blob
584058b48670057d91829ed0a788c70dcc6032f1
[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.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         // Channels which are not bound to any specific zone
128         connection.sendMCACCMemoryQuery();
129     }
130
131     /**
132      * Called when a Power OFF state update is received from the AVR.
133      */
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);
142
143         // Channels which are not bound to any specific zone
144         if (zone == 1) {
145             updateState(PioneerAvrBindingConstants.MCACC_MEMORY_CHANNEL, UnDefType.UNDEF);
146         }
147     }
148
149     /**
150      * Check the status of the AVR. Return true if the AVR is online, else return false.
151      *
152      * @return
153      */
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);
159         } else {
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);
165         }
166     }
167
168     /**
169      * Send a command to the AVR based on the openHAB command received.
170      */
171     @Override
172     public void handleCommand(ChannelUID channelUID, Command command) {
173         try {
174             boolean commandSent = false;
175             boolean unknownCommand = false;
176
177             if (channelUID.getId().contains(PioneerAvrBindingConstants.POWER_CHANNEL)) {
178                 if (command == RefreshType.REFRESH) {
179                     commandSent = connection.sendPowerQuery(getZoneFromChannelUID(channelUID.getId()));
180                 } else {
181                     commandSent = connection.sendPowerCommand(command, getZoneFromChannelUID(channelUID.getId()));
182                 }
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()));
187                 } else {
188                     commandSent = connection.sendVolumeCommand(command, getZoneFromChannelUID(channelUID.getId()));
189                 }
190             } else if (channelUID.getId().contains(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL)) {
191                 if (command == RefreshType.REFRESH) {
192                     commandSent = connection.sendInputSourceQuery(getZoneFromChannelUID(channelUID.getId()));
193                 } else {
194                     commandSent = connection.sendInputSourceCommand(command, getZoneFromChannelUID(channelUID.getId()));
195                 }
196             } else if (channelUID.getId().contains(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL)) {
197                 if (command == RefreshType.REFRESH) {
198                     commandSent = connection.sendListeningModeQuery(getZoneFromChannelUID(channelUID.getId()));
199                 } else {
200                     commandSent = connection.sendListeningModeCommand(command,
201                             getZoneFromChannelUID(channelUID.getId()));
202                 }
203             } else if (channelUID.getId().contains(PioneerAvrBindingConstants.MUTE_CHANNEL)) {
204                 if (command == RefreshType.REFRESH) {
205                     commandSent = connection.sendMuteQuery(getZoneFromChannelUID(channelUID.getId()));
206                 } else {
207                     commandSent = connection.sendMuteCommand(command, getZoneFromChannelUID(channelUID.getId()));
208                 }
209             } else if (channelUID.getId().contains(PioneerAvrBindingConstants.MCACC_MEMORY_CHANNEL)) {
210                 if (command == RefreshType.REFRESH) {
211                     commandSent = connection.sendMCACCMemoryQuery();
212                 } else {
213                     commandSent = connection.sendMCACCMemoryCommand(command);
214                 }
215             } else {
216                 unknownCommand = true;
217             }
218
219             // If the command is not unknown and has not been sent, the AVR is Offline
220             if (!commandSent && !unknownCommand) {
221                 onDisconnection();
222             }
223         } catch (CommandTypeNotSupportedException e) {
224             logger.warn("Unsupported command type received for channel {}.", channelUID.getId());
225         }
226     }
227
228     /**
229      * Called when a status update is received from the AVR.
230      */
231     @Override
232     public void statusUpdateReceived(AvrStatusUpdateEvent event) {
233         try {
234             AvrResponse response = RequestResponseFactory.getIpControlResponse(event.getData());
235
236             switch (response.getResponseType()) {
237                 case POWER_STATE:
238                     managePowerStateUpdate(response);
239                     break;
240
241                 case VOLUME_LEVEL:
242                     manageVolumeLevelUpdate(response);
243                     break;
244
245                 case MUTE_STATE:
246                     manageMuteStateUpdate(response);
247                     break;
248
249                 case INPUT_SOURCE_CHANNEL:
250                     manageInputSourceChannelUpdate(response);
251                     break;
252
253                 case LISTENING_MODE:
254                     manageListeningModeUpdate(response);
255                     break;
256
257                 case PLAYING_LISTENING_MODE:
258                     managePlayingListeningModeUpdate(response);
259                     break;
260
261                 case DISPLAY_INFORMATION:
262                     manageDisplayedInformationUpdate(response);
263                     break;
264
265                 case MCACC_MEMORY:
266                     manageMCACCMemoryUpdate(response);
267                     break;
268
269                 default:
270                     logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
271                             event.getConnection());
272             }
273         } catch (AvrConnectionException e) {
274             logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
275                     event.getConnection());
276         }
277     }
278
279     /**
280      * Called when the AVR is disconnected
281      */
282     @Override
283     public void onDisconnection(AvrDisconnectionEvent event) {
284         onDisconnection();
285     }
286
287     /**
288      * Process the AVR disconnection.
289      */
290     private void onDisconnection() {
291         updateStatus(ThingStatus.OFFLINE);
292     }
293
294     /**
295      * Notify an AVR power state update to openHAB
296      *
297      * @param response
298      */
299     private void managePowerStateUpdate(AvrResponse response) {
300         OnOffType state = OnOffType.from(PowerStateValues.ON_VALUE.equals(response.getParameterValue()));
301
302         // When a Power ON state update is received, call the onPowerOn method.
303         if (OnOffType.ON == state) {
304             onPowerOn(response.getZone());
305         } else {
306             onPowerOff(response.getZone());
307         }
308
309         updateState(getChannelUID(PioneerAvrBindingConstants.POWER_CHANNEL, response.getZone()), state);
310     }
311
312     /**
313      * Notify an AVR volume level update to openHAB
314      *
315      * @param response
316      */
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())));
323     }
324
325     /**
326      * Notify an AVR mute state update to openHAB
327      *
328      * @param response
329      */
330     private void manageMuteStateUpdate(AvrResponse response) {
331         updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, response.getZone()),
332                 OnOffType.from(!response.getParameterValue().equals(MuteStateValues.OFF_VALUE)));
333     }
334
335     /**
336      * Notify an AVR input source channel update to openHAB
337      *
338      * @param response
339      */
340     private void manageInputSourceChannelUpdate(AvrResponse response) {
341         updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, response.getZone()),
342                 new StringType(response.getParameterValue()));
343     }
344
345     /**
346      * Notify an AVR now-playing, in-effect listening mode (audio output format) update to openHAB
347      *
348      * @param response
349      */
350     private void managePlayingListeningModeUpdate(AvrResponse response) {
351         updateState(getChannelUID(PioneerAvrBindingConstants.PLAYING_LISTENING_MODE_CHANNEL, response.getZone()),
352                 new StringType(response.getParameterValue()));
353     }
354
355     /**
356      * Notify an AVR set listening mode (user-selected audio mode) update to openHAB
357      *
358      * @param response
359      */
360     private void manageListeningModeUpdate(AvrResponse response) {
361         updateState(getChannelUID(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL, response.getZone()),
362                 new StringType(response.getParameterValue()));
363     }
364
365     /**
366      * Notify an AVR displayed information update to openHAB
367      *
368      * @param response
369      */
370     private void manageDisplayedInformationUpdate(AvrResponse response) {
371         updateState(PioneerAvrBindingConstants.DISPLAY_INFORMATION_CHANNEL,
372                 new StringType(DisplayInformationConverter.convertMessageFromIpControl(response.getParameterValue())));
373     }
374
375     /**
376      * Notify an AVR MCACC Memory update to openHAB
377      *
378      * @param response
379      */
380     private void manageMCACCMemoryUpdate(AvrResponse response) {
381         updateState(PioneerAvrBindingConstants.MCACC_MEMORY_CHANNEL, new StringType(response.getParameterValue()));
382     }
383
384     /**
385      * Build the channelUID from the channel name and the zone number.
386      *
387      * @param channelName
388      * @param zone
389      * @return
390      */
391     protected String getChannelUID(String channelName, int zone) {
392         return String.format(PioneerAvrBindingConstants.GROUP_CHANNEL_PATTERN, zone, channelName);
393     }
394
395     /**
396      * Return the zone from the given channelUID.
397      *
398      * Return 0 if the zone cannot be extracted from the channelUID.
399      *
400      * @param channelUID
401      * @return
402      */
403     protected int getZoneFromChannelUID(String channelUID) {
404         int zone = 0;
405         Matcher matcher = PioneerAvrBindingConstants.GROUP_CHANNEL_ZONE_PATTERN.matcher(channelUID);
406         if (matcher.find()) {
407             zone = Integer.valueOf(matcher.group(1));
408         }
409         return zone;
410     }
411 }