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.controller;
15 import java.util.concurrent.atomic.AtomicInteger;
17 import org.openhab.binding.russound.internal.net.SocketSession;
18 import org.openhab.binding.russound.internal.rio.AbstractBridgeHandler;
19 import org.openhab.binding.russound.internal.rio.AbstractRioHandlerCallback;
20 import org.openhab.binding.russound.internal.rio.RioConstants;
21 import org.openhab.binding.russound.internal.rio.RioHandlerCallback;
22 import org.openhab.binding.russound.internal.rio.RioHandlerCallbackListener;
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.models.GsonUtilities;
26 import org.openhab.binding.russound.internal.rio.source.RioSourceHandler;
27 import org.openhab.binding.russound.internal.rio.system.RioSystemHandler;
28 import org.openhab.binding.russound.internal.rio.zone.RioZoneHandler;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.binding.ThingHandler;
35 import org.openhab.core.types.Command;
36 import org.openhab.core.types.RefreshType;
37 import org.openhab.core.types.State;
39 import com.google.gson.Gson;
42 * The bridge handler for a Russound Controller. A controller provides access to sources ({@link RioSourceHandler}) and
43 * zones ({@link RioZoneHandler}). This
44 * implementation must be attached to a {@link RioSystemHandler} bridge.
46 * @author Tim Roberts - Initial contribution
48 public class RioControllerHandler extends AbstractBridgeHandler<RioControllerProtocol> implements RioNamedHandler {
50 * The controller identifier of this instance (between 1-6)
52 private final AtomicInteger controller = new AtomicInteger(0);
55 * {@link Gson} used for JSON operations
57 private final Gson gson = GsonUtilities.createGson();
60 * Callback listener to use when zone name changes - will call {@link #refreshNamedHandler(Gson, Class, String)} to
61 * refresh the {@link RioConstants#CHANNEL_CTLZONES} channel
63 private final RioHandlerCallbackListener handlerCallbackListener = new RioHandlerCallbackListener() {
65 public void stateUpdate(String channelId, State state) {
66 refreshNamedHandler(gson, RioZoneHandler.class, RioConstants.CHANNEL_CTLZONES);
71 * Constructs the handler from the {@link Bridge}
73 * @param bridge a non-null {@link Bridge} the handler is for
75 public RioControllerHandler(Bridge bridge) {
80 * Returns the controller identifier
82 * @return the controller identifier
86 return controller.get();
90 * Returns the controller name
92 * @return a non-empty, non-null controller name
95 public String getName() {
96 return "Controller " + getId();
102 * Handles commands to specific channels. The only command this handles is a {@link RefreshType} and that's handled
103 * by {{@link #handleRefresh(String)}
106 public void handleCommand(ChannelUID channelUID, Command command) {
107 if (command instanceof RefreshType) {
108 handleRefresh(channelUID.getId());
114 * Method that handles the {@link RefreshType} command specifically.
116 * @param id a non-null, possibly empty channel id to refresh
118 private void handleRefresh(String id) {
119 if (id.equals(RioConstants.CHANNEL_CTLZONES)) {
120 refreshNamedHandler(gson, RioZoneHandler.class, RioConstants.CHANNEL_CTLZONES);
122 // Can't refresh any others...
126 * Initializes the bridge. Confirms the configuration is valid and that our parent bridge is a
127 * {@link RioSystemHandler}. Once validated, a {@link RioControllerProtocol} is set via
128 * {@link #setProtocolHandler(RioControllerProtocol)} and the bridge comes online.
131 public void initialize() {
132 final Bridge bridge = getBridge();
133 if (bridge == null) {
134 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
135 "Cannot be initialized without a bridge");
139 if (bridge.getStatus() != ThingStatus.ONLINE) {
140 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
144 final ThingHandler handler = bridge.getHandler();
145 if (handler == null) {
146 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
147 "No handler specified (null) for the bridge!");
151 if (!(handler instanceof RioSystemHandler)) {
152 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
153 "Controller must be attached to a system bridge: " + handler.getClass());
157 final RioControllerConfig config = getThing().getConfiguration().as(RioControllerConfig.class);
158 if (config == null) {
159 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
163 final int configController = config.getController();
164 if (configController < 1 || configController > 8) {
165 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
166 "Controller must be between 1 and 8: " + configController);
169 controller.set(configController);
171 // Get the socket session from the
172 final SocketSession socketSession = getSocketSession();
173 if (socketSession == null) {
174 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "No socket session found");
178 if (getProtocolHandler() != null) {
179 getProtocolHandler().dispose();
182 setProtocolHandler(new RioControllerProtocol(configController, socketSession,
183 new StatefulHandlerCallback(new AbstractRioHandlerCallback() {
185 public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
186 updateStatus(status, detail, msg);
190 public void stateChanged(String channelId, State state) {
191 updateState(channelId, state);
192 fireStateUpdated(channelId, state);
196 public void setProperty(String propertyName, String property) {
197 getThing().setProperty(propertyName, property);
201 updateStatus(ThingStatus.ONLINE);
202 getProtocolHandler().postOnline();
204 refreshNamedHandler(gson, RioZoneHandler.class, RioConstants.CHANNEL_CTLZONES);
208 * Overrides the base to call {@link #childChanged(ThingHandler)} to recreate the zone names
211 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
212 childChanged(childHandler, true);
216 * Overrides the base to call {@link #childChanged(ThingHandler)} to recreate the zone names
219 public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
220 childChanged(childHandler, false);
224 * Helper method to recreate the {@link RioConstants#CHANNEL_CTLZONES} channel
226 * @param childHandler a non-null child handler that changed
227 * @param added true if the handler was added, false otherwise
228 * @throw IllegalArgumentException if childHandler is null
230 private void childChanged(ThingHandler childHandler, boolean added) {
231 if (childHandler == null) {
232 throw new IllegalArgumentException("childHandler cannot be null");
234 if (childHandler instanceof RioZoneHandler) {
235 final RioHandlerCallback callback = ((RioZoneHandler) childHandler).getRioHandlerCallback();
236 if (callback != null) {
238 callback.addListener(RioConstants.CHANNEL_ZONENAME, handlerCallbackListener);
240 callback.removeListener(RioConstants.CHANNEL_ZONENAME, handlerCallbackListener);
243 refreshNamedHandler(gson, RioZoneHandler.class, RioConstants.CHANNEL_CTLZONES);