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.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.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;
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.
43 * @author Tim Roberts - Initial contribution
45 public class RioSourceHandler extends AbstractThingHandler<RioSourceProtocol> implements RioNamedHandler {
47 private final Logger logger = LoggerFactory.getLogger(RioSourceHandler.class);
50 * The source identifier for this instance (1-12)
52 private final AtomicInteger source = new AtomicInteger(0);
57 private final AtomicReference<String> sourceName = new AtomicReference<>(null);
60 * Constructs the handler from the {@link Thing}
62 * @param thing a non-null {@link Thing} the handler is for
64 public RioSourceHandler(Thing thing) {
69 * Returns the source identifier for this instance
71 * @return the source identifier
79 * Returns the source name for this instance
81 * @return the source name
84 public String getName() {
85 final String name = sourceName.get();
86 return name == null || name.isEmpty() ? "Source " + getId() : name;
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
99 public void handleCommand(ChannelUID channelUID, Command command) {
100 if (command instanceof RefreshType) {
101 handleRefresh(channelUID.getId());
105 // if (getThing().getStatus() != ThingStatus.ONLINE) {
106 // // Ignore any command if not online
110 String id = channelUID.getId();
113 logger.debug("Called with a null channel id - ignoring");
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);
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);
128 logger.debug("Received a BANKS channel command with a non StringType: {}", command);
131 logger.debug("Unknown/Unsupported Channel id: {}", id);
136 * Method that handles the {@link RefreshType} command specifically. Calls the {@link RioSourceProtocol} to
137 * handle the actual refresh based on the channel id.
139 * @param id a non-null, possibly empty channel id to refresh
141 private void handleRefresh(String id) {
142 if (getThing().getStatus() != ThingStatus.ONLINE) {
146 if (getProtocolHandler() == null) {
150 // Remove the cache'd value to force a refreshed value
151 ((StatefulHandlerCallback) getProtocolHandler().getCallback()).removeState(id);
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();
196 // Can't refresh any others...
200 * Initializes the bridge. Confirms the configuration is valid and that our parent bridge is a
201 * {@link RioSystemHandler}. Once validated, a
202 * {@link org.openhab.binding.russound.internal.rio.system.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());