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.russound.internal.rio.source;
15 import java.util.concurrent.TimeUnit;
16 import java.util.concurrent.atomic.AtomicInteger;
17 import java.util.concurrent.atomic.AtomicReference;
19 import org.apache.commons.lang.StringUtils;
20 import org.openhab.binding.russound.internal.net.SocketSession;
21 import org.openhab.binding.russound.internal.rio.AbstractRioHandlerCallback;
22 import org.openhab.binding.russound.internal.rio.AbstractThingHandler;
23 import org.openhab.binding.russound.internal.rio.RioConstants;
24 import org.openhab.binding.russound.internal.rio.RioNamedHandler;
25 import org.openhab.binding.russound.internal.rio.StatefulHandlerCallback;
26 import org.openhab.binding.russound.internal.rio.system.RioSystemHandler;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.thing.Bridge;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.binding.ThingHandler;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.RefreshType;
36 import org.openhab.core.types.State;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
41 * The bridge handler for a Russound Source. A source provides source music to the russound system (along with metadata
42 * about the streaming music). This implementation must be attached to a {@link RioSystemHandler} bridge.
44 * @author Tim Roberts - Initial contribution
46 public class RioSourceHandler extends AbstractThingHandler<RioSourceProtocol> implements RioNamedHandler {
48 private final Logger logger = LoggerFactory.getLogger(RioSourceHandler.class);
51 * The source identifier for this instance (1-12)
53 private final AtomicInteger source = new AtomicInteger(0);
58 private final AtomicReference<String> sourceName = new AtomicReference<>(null);
61 * Constructs the handler from the {@link Thing}
63 * @param thing a non-null {@link Thing} the handler is for
65 public RioSourceHandler(Thing thing) {
70 * Returns the source identifier for this instance
72 * @return the source identifier
80 * Returns the source name for this instance
82 * @return the source name
85 public String getName() {
86 final String name = sourceName.get();
87 return StringUtils.isEmpty(name) ? ("Source " + getId()) : name;
93 * Handles commands to specific channels. This implementation will offload much of its work to the
94 * {@link RioSourceProtocol}. Basically we validate the type of command for the channel then call the
95 * {@link RioSourceProtocol} to handle the actual protocol. Special use case is the {@link RefreshType}
96 * where we call {{@link #handleRefresh(String)} to handle a refresh of the specific channel (which in turn calls
97 * {@link RioSourceProtocol} to handle the actual refresh
100 public void handleCommand(ChannelUID channelUID, Command command) {
101 if (command instanceof RefreshType) {
102 handleRefresh(channelUID.getId());
106 // if (getThing().getStatus() != ThingStatus.ONLINE) {
107 // // Ignore any command if not online
111 String id = channelUID.getId();
114 logger.debug("Called with a null channel id - ignoring");
118 if (id.equals(RioConstants.CHANNEL_SOURCEBANKS)) {
119 if (command instanceof StringType) {
120 // Remove any state for this channel to ensure it's recreated/sent again
121 // (clears any bad or deleted favorites information from the channel)
122 ((StatefulHandlerCallback) getProtocolHandler().getCallback())
123 .removeState(RioConstants.CHANNEL_SOURCEBANKS);
125 // schedule the returned callback in the future (to allow the channel to process and to allow russound
126 // to process (before re-retrieving information)
127 scheduler.schedule(getProtocolHandler().setBanks(command.toString()), 250, TimeUnit.MILLISECONDS);
129 logger.debug("Received a BANKS channel command with a non StringType: {}", command);
132 logger.debug("Unknown/Unsupported Channel id: {}", id);
137 * Method that handles the {@link RefreshType} command specifically. Calls the {@link RioSourceProtocol} to
138 * handle the actual refresh based on the channel id.
140 * @param id a non-null, possibly empty channel id to refresh
142 private void handleRefresh(String id) {
143 if (getThing().getStatus() != ThingStatus.ONLINE) {
147 if (getProtocolHandler() == null) {
151 // Remove the cache'd value to force a refreshed value
152 ((StatefulHandlerCallback) getProtocolHandler().getCallback()).removeState(id);
154 if (id.equals(RioConstants.CHANNEL_SOURCENAME)) {
155 getProtocolHandler().refreshSourceName();
156 } else if (id.startsWith(RioConstants.CHANNEL_SOURCETYPE)) {
157 getProtocolHandler().refreshSourceType();
158 } else if (id.startsWith(RioConstants.CHANNEL_SOURCECOMPOSERNAME)) {
159 getProtocolHandler().refreshSourceComposerName();
160 } else if (id.startsWith(RioConstants.CHANNEL_SOURCECHANNEL)) {
161 getProtocolHandler().refreshSourceChannel();
162 } else if (id.startsWith(RioConstants.CHANNEL_SOURCECHANNELNAME)) {
163 getProtocolHandler().refreshSourceChannelName();
164 } else if (id.startsWith(RioConstants.CHANNEL_SOURCEGENRE)) {
165 getProtocolHandler().refreshSourceGenre();
166 } else if (id.startsWith(RioConstants.CHANNEL_SOURCEARTISTNAME)) {
167 getProtocolHandler().refreshSourceArtistName();
168 } else if (id.startsWith(RioConstants.CHANNEL_SOURCEALBUMNAME)) {
169 getProtocolHandler().refreshSourceAlbumName();
170 } else if (id.startsWith(RioConstants.CHANNEL_SOURCECOVERARTURL)) {
171 getProtocolHandler().refreshSourceCoverArtUrl();
172 } else if (id.startsWith(RioConstants.CHANNEL_SOURCEPLAYLISTNAME)) {
173 getProtocolHandler().refreshSourcePlaylistName();
174 } else if (id.startsWith(RioConstants.CHANNEL_SOURCESONGNAME)) {
175 getProtocolHandler().refreshSourceSongName();
176 } else if (id.startsWith(RioConstants.CHANNEL_SOURCEMODE)) {
177 getProtocolHandler().refreshSourceMode();
178 } else if (id.startsWith(RioConstants.CHANNEL_SOURCESHUFFLEMODE)) {
179 getProtocolHandler().refreshSourceShuffleMode();
180 } else if (id.startsWith(RioConstants.CHANNEL_SOURCEREPEATMODE)) {
181 getProtocolHandler().refreshSourceRepeatMode();
182 } else if (id.startsWith(RioConstants.CHANNEL_SOURCERATING)) {
183 getProtocolHandler().refreshSourceRating();
184 } else if (id.startsWith(RioConstants.CHANNEL_SOURCEPROGRAMSERVICENAME)) {
185 getProtocolHandler().refreshSourceProgramServiceName();
186 } else if (id.startsWith(RioConstants.CHANNEL_SOURCERADIOTEXT)) {
187 getProtocolHandler().refreshSourceRadioText();
188 } else if (id.startsWith(RioConstants.CHANNEL_SOURCERADIOTEXT2)) {
189 getProtocolHandler().refreshSourceRadioText2();
190 } else if (id.startsWith(RioConstants.CHANNEL_SOURCERADIOTEXT3)) {
191 getProtocolHandler().refreshSourceRadioText3();
192 } else if (id.startsWith(RioConstants.CHANNEL_SOURCERADIOTEXT4)) {
193 getProtocolHandler().refreshSourceRadioText4();
194 } else if (id.equals(RioConstants.CHANNEL_SOURCEBANKS)) {
195 getProtocolHandler().refreshBanks();
197 // Can't refresh any others...
201 * Initializes the bridge. Confirms the configuration is valid and that our parent bridge is a
202 * {@link RioSystemHandler}. Once validated, a {@link RioSystemProtocol} is set via
203 * {@link #setProtocolHandler(RioSystemProtocol)} and the bridge comes online.
206 public void initialize() {
207 logger.debug("Initializing");
208 final Bridge bridge = getBridge();
209 if (bridge == null) {
210 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
211 "Cannot be initialized without a bridge");
214 if (bridge.getStatus() != ThingStatus.ONLINE) {
215 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
219 final ThingHandler handler = bridge.getHandler();
220 if (handler == null) {
221 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
222 "No handler specified (null) for the bridge!");
226 if (!(handler instanceof RioSystemHandler)) {
227 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
228 "Source must be attached to a System bridge: " + handler.getClass());
232 final RioSourceConfig config = getThing().getConfiguration().as(RioSourceConfig.class);
233 if (config == null) {
234 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
238 final int configSource = config.getSource();
239 if (configSource < 1 || configSource > 12) {
240 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
241 "Source must be between 1 and 12: " + configSource);
244 source.set(configSource);
246 // Get the socket session from the
247 final SocketSession socketSession = getSocketSession();
248 if (socketSession == null) {
249 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "No socket session found");
254 setProtocolHandler(new RioSourceProtocol(configSource, socketSession,
255 new StatefulHandlerCallback(new AbstractRioHandlerCallback() {
257 public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
258 updateStatus(status, detail, msg);
262 public void stateChanged(String channelId, State state) {
263 if (channelId.equals(RioConstants.CHANNEL_SOURCENAME)) {
264 sourceName.set(state.toString());
268 updateState(channelId, state);
269 fireStateUpdated(channelId, state);
274 public void setProperty(String propertyName, String propertyValue) {
275 getThing().setProperty(propertyName, propertyValue);
279 updateStatus(ThingStatus.ONLINE);
280 getProtocolHandler().postOnline();
281 } catch (Exception e) {
282 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());