]> git.basschouten.com Git - openhab-addons.git/blob
8bf3f29fab9c07d3009c8469dcb0695c96aabd3d
[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.controller;
14
15 import java.util.concurrent.atomic.AtomicInteger;
16
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;
38
39 import com.google.gson.Gson;
40
41 /**
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.
45  *
46  * @author Tim Roberts - Initial contribution
47  */
48 public class RioControllerHandler extends AbstractBridgeHandler<RioControllerProtocol> implements RioNamedHandler {
49     /**
50      * The controller identifier of this instance (between 1-6)
51      */
52     private final AtomicInteger controller = new AtomicInteger(0);
53
54     /**
55      * {@link Gson} used for JSON operations
56      */
57     private final Gson gson = GsonUtilities.createGson();
58
59     /**
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
62      */
63     private final RioHandlerCallbackListener handlerCallbackListener = new RioHandlerCallbackListener() {
64         @Override
65         public void stateUpdate(String channelId, State state) {
66             refreshNamedHandler(gson, RioZoneHandler.class, RioConstants.CHANNEL_CTLZONES);
67         }
68     };
69
70     /**
71      * Constructs the handler from the {@link Bridge}
72      *
73      * @param bridge a non-null {@link Bridge} the handler is for
74      */
75     public RioControllerHandler(Bridge bridge) {
76         super(bridge);
77     }
78
79     /**
80      * Returns the controller identifier
81      *
82      * @return the controller identifier
83      */
84     @Override
85     public int getId() {
86         return controller.get();
87     }
88
89     /**
90      * Returns the controller name
91      *
92      * @return a non-empty, non-null controller name
93      */
94     @Override
95     public String getName() {
96         return "Controller " + getId();
97     }
98
99     /**
100      * {@inheritDoc}
101      *
102      * Handles commands to specific channels. The only command this handles is a {@link RefreshType} and that's handled
103      * by {{@link #handleRefresh(String)}
104      */
105     @Override
106     public void handleCommand(ChannelUID channelUID, Command command) {
107         if (command instanceof RefreshType) {
108             handleRefresh(channelUID.getId());
109             return;
110         }
111     }
112
113     /**
114      * Method that handles the {@link RefreshType} command specifically.
115      *
116      * @param id a non-null, possibly empty channel id to refresh
117      */
118     private void handleRefresh(String id) {
119         if (id.equals(RioConstants.CHANNEL_CTLZONES)) {
120             refreshNamedHandler(gson, RioZoneHandler.class, RioConstants.CHANNEL_CTLZONES);
121         }
122         // Can't refresh any others...
123     }
124
125     /**
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(AbstractRioProtocol)} and the bridge comes online.
129      */
130     @Override
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");
136             return;
137         }
138
139         if (bridge.getStatus() != ThingStatus.ONLINE) {
140             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
141             return;
142         }
143
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!");
148             return;
149         }
150
151         if (!(handler instanceof RioSystemHandler)) {
152             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
153                     "Controller must be attached to a system bridge: " + handler.getClass());
154             return;
155         }
156
157         final RioControllerConfig config = getThing().getConfiguration().as(RioControllerConfig.class);
158         if (config == null) {
159             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
160             return;
161         }
162
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);
167             return;
168         }
169         controller.set(configController);
170
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");
175             return;
176         }
177
178         if (getProtocolHandler() != null) {
179             getProtocolHandler().dispose();
180         }
181
182         setProtocolHandler(new RioControllerProtocol(configController, socketSession,
183                 new StatefulHandlerCallback(new AbstractRioHandlerCallback() {
184                     @Override
185                     public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
186                         updateStatus(status, detail, msg);
187                     }
188
189                     @Override
190                     public void stateChanged(String channelId, State state) {
191                         updateState(channelId, state);
192                         fireStateUpdated(channelId, state);
193                     }
194
195                     @Override
196                     public void setProperty(String propertyName, String property) {
197                         getThing().setProperty(propertyName, property);
198                     }
199                 })));
200
201         updateStatus(ThingStatus.ONLINE);
202         getProtocolHandler().postOnline();
203
204         refreshNamedHandler(gson, RioZoneHandler.class, RioConstants.CHANNEL_CTLZONES);
205     }
206
207     /**
208      * Overrides the base to call {@link #childChanged(ThingHandler, boolean)} to recreate the zone names
209      */
210     @Override
211     public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
212         childChanged(childHandler, true);
213     }
214
215     /**
216      * Overrides the base to call {@link #childChanged(ThingHandler, boolean)} to recreate the zone names
217      */
218     @Override
219     public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
220         childChanged(childHandler, false);
221     }
222
223     /**
224      * Helper method to recreate the {@link RioConstants#CHANNEL_CTLZONES} channel
225      *
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
229      */
230     private void childChanged(ThingHandler childHandler, boolean added) {
231         if (childHandler == null) {
232             throw new IllegalArgumentException("childHandler cannot be null");
233         }
234         if (childHandler instanceof RioZoneHandler zoneHandler) {
235             final RioHandlerCallback callback = zoneHandler.getRioHandlerCallback();
236             if (callback != null) {
237                 if (added) {
238                     callback.addListener(RioConstants.CHANNEL_ZONENAME, handlerCallbackListener);
239                 } else {
240                     callback.removeListener(RioConstants.CHANNEL_ZONENAME, handlerCallbackListener);
241                 }
242             }
243             refreshNamedHandler(gson, RioZoneHandler.class, RioConstants.CHANNEL_CTLZONES);
244         }
245     }
246 }