]> git.basschouten.com Git - openhab-addons.git/blob
8ba1f2db72992cb09e72d6da405f8cd297e13059
[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.russound.internal.rio.source;
14
15 import java.util.concurrent.TimeUnit;
16 import java.util.concurrent.atomic.AtomicInteger;
17 import java.util.concurrent.atomic.AtomicReference;
18
19 import org.openhab.binding.russound.internal.net.SocketSession;
20 import org.openhab.binding.russound.internal.rio.AbstractRioHandlerCallback;
21 import org.openhab.binding.russound.internal.rio.AbstractThingHandler;
22 import org.openhab.binding.russound.internal.rio.RioConstants;
23 import org.openhab.binding.russound.internal.rio.RioNamedHandler;
24 import org.openhab.binding.russound.internal.rio.StatefulHandlerCallback;
25 import org.openhab.binding.russound.internal.rio.system.RioSystemHandler;
26 import org.openhab.core.library.types.StringType;
27 import org.openhab.core.thing.Bridge;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.thing.binding.ThingHandler;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.openhab.core.types.State;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 /**
40  * The bridge handler for a Russound Source. A source provides source music to the russound system (along with metadata
41  * about the streaming music). This implementation must be attached to a {@link RioSystemHandler} bridge.
42  *
43  * @author Tim Roberts - Initial contribution
44  */
45 public class RioSourceHandler extends AbstractThingHandler<RioSourceProtocol> implements RioNamedHandler {
46     // Logger
47     private final Logger logger = LoggerFactory.getLogger(RioSourceHandler.class);
48
49     /**
50      * The source identifier for this instance (1-12)
51      */
52     private final AtomicInteger source = new AtomicInteger(0);
53
54     /**
55      * The source name
56      */
57     private final AtomicReference<String> sourceName = new AtomicReference<>(null);
58
59     /**
60      * Constructs the handler from the {@link Thing}
61      *
62      * @param thing a non-null {@link Thing} the handler is for
63      */
64     public RioSourceHandler(Thing thing) {
65         super(thing);
66     }
67
68     /**
69      * Returns the source identifier for this instance
70      *
71      * @return the source identifier
72      */
73     @Override
74     public int getId() {
75         return source.get();
76     }
77
78     /**
79      * Returns the source name for this instance
80      *
81      * @return the source name
82      */
83     @Override
84     public String getName() {
85         final String name = sourceName.get();
86         return name == null || name.isEmpty() ? "Source " + getId() : name;
87     }
88
89     /**
90      * {@inheritDoc}
91      *
92      * Handles commands to specific channels. This implementation will offload much of its work to the
93      * {@link RioSourceProtocol}. Basically we validate the type of command for the channel then call the
94      * {@link RioSourceProtocol} to handle the actual protocol. Special use case is the {@link RefreshType}
95      * where we call {{@link #handleRefresh(String)} to handle a refresh of the specific channel (which in turn calls
96      * {@link RioSourceProtocol} to handle the actual refresh
97      */
98     @Override
99     public void handleCommand(ChannelUID channelUID, Command command) {
100         if (command instanceof RefreshType) {
101             handleRefresh(channelUID.getId());
102             return;
103         }
104
105         // if (getThing().getStatus() != ThingStatus.ONLINE) {
106         // // Ignore any command if not online
107         // return;
108         // }
109
110         String id = channelUID.getId();
111
112         if (id == null) {
113             logger.debug("Called with a null channel id - ignoring");
114             return;
115         }
116
117         if (id.equals(RioConstants.CHANNEL_SOURCEBANKS)) {
118             if (command instanceof StringType) {
119                 // Remove any state for this channel to ensure it's recreated/sent again
120                 // (clears any bad or deleted favorites information from the channel)
121                 ((StatefulHandlerCallback) getProtocolHandler().getCallback())
122                         .removeState(RioConstants.CHANNEL_SOURCEBANKS);
123
124                 // schedule the returned callback in the future (to allow the channel to process and to allow russound
125                 // to process (before re-retrieving information)
126                 scheduler.schedule(getProtocolHandler().setBanks(command.toString()), 250, TimeUnit.MILLISECONDS);
127             } else {
128                 logger.debug("Received a BANKS channel command with a non StringType: {}", command);
129             }
130         } else {
131             logger.debug("Unknown/Unsupported Channel id: {}", id);
132         }
133     }
134
135     /**
136      * Method that handles the {@link RefreshType} command specifically. Calls the {@link RioSourceProtocol} to
137      * handle the actual refresh based on the channel id.
138      *
139      * @param id a non-null, possibly empty channel id to refresh
140      */
141     private void handleRefresh(String id) {
142         if (getThing().getStatus() != ThingStatus.ONLINE) {
143             return;
144         }
145
146         if (getProtocolHandler() == null) {
147             return;
148         }
149
150         // Remove the cache'd value to force a refreshed value
151         ((StatefulHandlerCallback) getProtocolHandler().getCallback()).removeState(id);
152
153         if (id.equals(RioConstants.CHANNEL_SOURCENAME)) {
154             getProtocolHandler().refreshSourceName();
155         } else if (id.startsWith(RioConstants.CHANNEL_SOURCETYPE)) {
156             getProtocolHandler().refreshSourceType();
157         } else if (id.startsWith(RioConstants.CHANNEL_SOURCECOMPOSERNAME)) {
158             getProtocolHandler().refreshSourceComposerName();
159         } else if (id.startsWith(RioConstants.CHANNEL_SOURCECHANNEL)) {
160             getProtocolHandler().refreshSourceChannel();
161         } else if (id.startsWith(RioConstants.CHANNEL_SOURCECHANNELNAME)) {
162             getProtocolHandler().refreshSourceChannelName();
163         } else if (id.startsWith(RioConstants.CHANNEL_SOURCEGENRE)) {
164             getProtocolHandler().refreshSourceGenre();
165         } else if (id.startsWith(RioConstants.CHANNEL_SOURCEARTISTNAME)) {
166             getProtocolHandler().refreshSourceArtistName();
167         } else if (id.startsWith(RioConstants.CHANNEL_SOURCEALBUMNAME)) {
168             getProtocolHandler().refreshSourceAlbumName();
169         } else if (id.startsWith(RioConstants.CHANNEL_SOURCECOVERARTURL)) {
170             getProtocolHandler().refreshSourceCoverArtUrl();
171         } else if (id.startsWith(RioConstants.CHANNEL_SOURCEPLAYLISTNAME)) {
172             getProtocolHandler().refreshSourcePlaylistName();
173         } else if (id.startsWith(RioConstants.CHANNEL_SOURCESONGNAME)) {
174             getProtocolHandler().refreshSourceSongName();
175         } else if (id.startsWith(RioConstants.CHANNEL_SOURCEMODE)) {
176             getProtocolHandler().refreshSourceMode();
177         } else if (id.startsWith(RioConstants.CHANNEL_SOURCESHUFFLEMODE)) {
178             getProtocolHandler().refreshSourceShuffleMode();
179         } else if (id.startsWith(RioConstants.CHANNEL_SOURCEREPEATMODE)) {
180             getProtocolHandler().refreshSourceRepeatMode();
181         } else if (id.startsWith(RioConstants.CHANNEL_SOURCERATING)) {
182             getProtocolHandler().refreshSourceRating();
183         } else if (id.startsWith(RioConstants.CHANNEL_SOURCEPROGRAMSERVICENAME)) {
184             getProtocolHandler().refreshSourceProgramServiceName();
185         } else if (id.startsWith(RioConstants.CHANNEL_SOURCERADIOTEXT)) {
186             getProtocolHandler().refreshSourceRadioText();
187         } else if (id.startsWith(RioConstants.CHANNEL_SOURCERADIOTEXT2)) {
188             getProtocolHandler().refreshSourceRadioText2();
189         } else if (id.startsWith(RioConstants.CHANNEL_SOURCERADIOTEXT3)) {
190             getProtocolHandler().refreshSourceRadioText3();
191         } else if (id.startsWith(RioConstants.CHANNEL_SOURCERADIOTEXT4)) {
192             getProtocolHandler().refreshSourceRadioText4();
193         } else if (id.equals(RioConstants.CHANNEL_SOURCEBANKS)) {
194             getProtocolHandler().refreshBanks();
195         }
196         // Can't refresh any others...
197     }
198
199     /**
200      * Initializes the bridge. Confirms the configuration is valid and that our parent bridge is a
201      * {@link RioSystemHandler}. Once validated, a {@link RioSystemProtocol} is set via
202      * {@link #setProtocolHandler(RioSystemProtocol)} and the bridge comes online.
203      */
204     @Override
205     public void initialize() {
206         logger.debug("Initializing");
207         final Bridge bridge = getBridge();
208         if (bridge == null) {
209             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
210                     "Cannot be initialized without a bridge");
211             return;
212         }
213         if (bridge.getStatus() != ThingStatus.ONLINE) {
214             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
215             return;
216         }
217
218         final ThingHandler handler = bridge.getHandler();
219         if (handler == null) {
220             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
221                     "No handler specified (null) for the bridge!");
222             return;
223         }
224
225         if (!(handler instanceof RioSystemHandler)) {
226             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
227                     "Source must be attached to a System bridge: " + handler.getClass());
228             return;
229         }
230
231         final RioSourceConfig config = getThing().getConfiguration().as(RioSourceConfig.class);
232         if (config == null) {
233             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
234             return;
235         }
236
237         final int configSource = config.getSource();
238         if (configSource < 1 || configSource > 12) {
239             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
240                     "Source must be between 1 and 12: " + configSource);
241             return;
242         }
243         source.set(configSource);
244
245         // Get the socket session from the
246         final SocketSession socketSession = getSocketSession();
247         if (socketSession == null) {
248             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "No socket session found");
249             return;
250         }
251
252         try {
253             setProtocolHandler(new RioSourceProtocol(configSource, socketSession,
254                     new StatefulHandlerCallback(new AbstractRioHandlerCallback() {
255                         @Override
256                         public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
257                             updateStatus(status, detail, msg);
258                         }
259
260                         @Override
261                         public void stateChanged(String channelId, State state) {
262                             if (channelId.equals(RioConstants.CHANNEL_SOURCENAME)) {
263                                 sourceName.set(state.toString());
264                             }
265
266                             if (state != null) {
267                                 updateState(channelId, state);
268                                 fireStateUpdated(channelId, state);
269                             }
270                         }
271
272                         @Override
273                         public void setProperty(String propertyName, String propertyValue) {
274                             getThing().setProperty(propertyName, propertyValue);
275                         }
276                     })));
277
278             updateStatus(ThingStatus.ONLINE);
279             getProtocolHandler().postOnline();
280         } catch (Exception e) {
281             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
282         }
283     }
284 }