]> git.basschouten.com Git - openhab-addons.git/blob
0b2b1a43c7c36abcbf9a8d2cba1b871656af9fc7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.neeo.internal.handler;
14
15 import java.io.IOException;
16 import java.util.HashMap;
17 import java.util.Map;
18 import java.util.Objects;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.atomic.AtomicReference;
22
23 import org.apache.commons.lang.StringUtils;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.neeo.internal.NeeoBrainApi;
27 import org.openhab.binding.neeo.internal.NeeoConstants;
28 import org.openhab.binding.neeo.internal.NeeoHandlerCallback;
29 import org.openhab.binding.neeo.internal.NeeoRoomConfig;
30 import org.openhab.binding.neeo.internal.NeeoRoomProtocol;
31 import org.openhab.binding.neeo.internal.NeeoUtil;
32 import org.openhab.binding.neeo.internal.UidUtils;
33 import org.openhab.binding.neeo.internal.models.NeeoAction;
34 import org.openhab.binding.neeo.internal.models.NeeoRoom;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.ThingUID;
41 import org.openhab.core.thing.binding.BaseBridgeHandler;
42 import org.openhab.core.thing.binding.BridgeHandler;
43 import org.openhab.core.thing.binding.builder.ThingBuilder;
44 import org.openhab.core.types.Command;
45 import org.openhab.core.types.RefreshType;
46 import org.openhab.core.types.State;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51  * A subclass of {@link BaseBridgeHandler} that is responsible for handling commands for a room
52  *
53  * @author Tim Roberts - Initial contribution
54  */
55 @NonNullByDefault
56 public class NeeoRoomHandler extends BaseBridgeHandler {
57
58     /** The logger */
59     private final Logger logger = LoggerFactory.getLogger(NeeoRoomHandler.class);
60
61     /**
62      * The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
63      */
64     private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>();
65
66     /**
67      * The refresh task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
68      */
69     private final AtomicReference<@Nullable Future<?>> refreshTask = new AtomicReference<>();
70
71     /** The {@link NeeoRoomProtocol} (null until set by {@link #initializationTask}) */
72     private final AtomicReference<@Nullable NeeoRoomProtocol> roomProtocol = new AtomicReference<>();
73
74     /**
75      * Instantiates a new neeo room handler.
76      *
77      * @param bridge the non-null bridge
78      */
79     NeeoRoomHandler(Bridge bridge) {
80         super(bridge);
81         Objects.requireNonNull(bridge, "bridge cannot be null");
82     }
83
84     @Override
85     public void handleCommand(ChannelUID channelUID, Command command) {
86         Objects.requireNonNull(channelUID, "channelUID cannot be null");
87         Objects.requireNonNull(command, "command cannot be null");
88
89         final NeeoRoomProtocol protocol = roomProtocol.get();
90         if (protocol == null) {
91             logger.debug("Protocol is null - ignoring update: {}", channelUID);
92             return;
93         }
94
95         final String[] channelIds = UidUtils.parseChannelId(channelUID);
96         if (channelIds.length == 0) {
97             logger.debug("Bad group declaration: {}", channelUID);
98             return;
99         }
100
101         final String localGroupId = channelUID.getGroupId();
102         final String groupId = localGroupId == null || StringUtils.isEmpty(localGroupId) ? "" : localGroupId;
103         final String channelId = channelIds[0];
104         final String channelKey = channelIds.length > 1 ? channelIds[1] : "";
105
106         if (command instanceof RefreshType) {
107             refreshChannel(protocol, groupId, channelKey, channelId);
108         } else {
109             switch (groupId) {
110                 case NeeoConstants.ROOM_GROUP_RECIPE_ID:
111                     switch (channelId) {
112                         case NeeoConstants.ROOM_CHANNEL_STATUS:
113                             // Ignore OFF status updates
114                             if (command == OnOffType.ON) {
115                                 protocol.startRecipe(channelKey);
116                             }
117                             break;
118                     }
119                     break;
120                 case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
121                     switch (channelId) {
122                         case NeeoConstants.ROOM_CHANNEL_STATUS:
123                             if (command instanceof OnOffType) {
124                                 protocol.setScenarioStatus(channelKey, command == OnOffType.ON);
125                             }
126                             break;
127                     }
128                     break;
129                 default:
130                     logger.debug("Unknown channel to set: {}", channelUID);
131                     break;
132             }
133         }
134     }
135
136     /**
137      * Refresh the specified channel section, key and id using the specified protocol
138      *
139      * @param protocol a non-null protocol to use
140      * @param groupId the non-empty channel section
141      * @param channelKey the non-empty channel key
142      * @param channelId the non-empty channel id
143      */
144     private void refreshChannel(NeeoRoomProtocol protocol, String groupId, String channelKey, String channelId) {
145         Objects.requireNonNull(protocol, "protocol cannot be null");
146         NeeoUtil.requireNotEmpty(groupId, "groupId must not be empty");
147         NeeoUtil.requireNotEmpty(channelId, "channelId must not be empty");
148
149         switch (groupId) {
150             case NeeoConstants.ROOM_GROUP_RECIPE_ID:
151                 NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
152                 switch (channelId) {
153                     case NeeoConstants.ROOM_CHANNEL_NAME:
154                         protocol.refreshRecipeName(channelKey);
155                         break;
156                     case NeeoConstants.ROOM_CHANNEL_TYPE:
157                         protocol.refreshRecipeType(channelKey);
158                         break;
159                     case NeeoConstants.ROOM_CHANNEL_ENABLED:
160                         protocol.refreshRecipeEnabled(channelKey);
161                         break;
162                     case NeeoConstants.ROOM_CHANNEL_STATUS:
163                         protocol.refreshRecipeStatus(channelKey);
164                         break;
165                 }
166                 break;
167             case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
168                 NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
169                 switch (channelId) {
170                     case NeeoConstants.ROOM_CHANNEL_NAME:
171                         protocol.refreshScenarioName(channelKey);
172                         break;
173                     case NeeoConstants.ROOM_CHANNEL_CONFIGURED:
174                         protocol.refreshScenarioConfigured(channelKey);
175                         break;
176                     case NeeoConstants.ROOM_CHANNEL_STATUS:
177                         protocol.refreshScenarioStatus(channelKey);
178                         break;
179                 }
180                 break;
181         }
182     }
183
184     @Override
185     public void initialize() {
186         NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
187             initializeTask();
188         })));
189     }
190
191     /**
192      * Initializes the task be creating the {@link NeeoRoomProtocol}, going online and then scheduling the refresh task.
193      */
194     private void initializeTask() {
195         final NeeoRoomConfig config = getConfigAs(NeeoRoomConfig.class);
196
197         final String roomKey = config.getRoomKey();
198         if (roomKey == null || StringUtils.isEmpty(roomKey)) {
199             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
200                     "Room key (from the parent room bridge) was not found");
201             return;
202         }
203
204         try {
205             NeeoUtil.checkInterrupt();
206             final NeeoBrainApi brainApi = getNeeoBrainApi();
207             if (brainApi == null) {
208                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Cannot find the NEEO Brain API");
209                 return;
210             }
211
212             final NeeoRoom room = brainApi.getRoom(roomKey);
213
214             final ThingUID thingUid = getThing().getUID();
215
216             final Map<String, String> properties = new HashMap<>();
217             properties.put("Key", roomKey);
218
219             final ThingBuilder thingBuilder = editThing();
220             thingBuilder.withLabel(room.getName() + " (NEEO " + brainApi.getBrain().getKey() + ")")
221                     .withProperties(properties).withChannels(ChannelUtils.generateChannels(thingUid, room));
222             updateThing(thingBuilder.build());
223
224             NeeoUtil.checkInterrupt();
225             final NeeoRoomProtocol protocol = new NeeoRoomProtocol(new NeeoHandlerCallback() {
226
227                 @Override
228                 public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
229                     updateStatus(status, detail, msg);
230                 }
231
232                 @Override
233                 public void stateChanged(String channelId, State state) {
234                     updateState(channelId, state);
235                 }
236
237                 @Override
238                 public void setProperty(String propertyName, String propertyValue) {
239                     getThing().setProperty(propertyName, propertyValue);
240                 }
241
242                 @Override
243                 public void scheduleTask(Runnable task, long milliSeconds) {
244                     scheduler.schedule(task, milliSeconds, TimeUnit.MILLISECONDS);
245                 }
246
247                 @Override
248                 public void triggerEvent(String channelID, String event) {
249                     triggerChannel(channelID, event);
250                 }
251
252                 @Nullable
253                 @Override
254                 public NeeoBrainApi getApi() {
255                     return getNeeoBrainApi();
256                 }
257             }, roomKey);
258             roomProtocol.getAndSet(protocol);
259
260             NeeoUtil.checkInterrupt();
261             updateStatus(ThingStatus.ONLINE);
262
263             if (config.getRefreshPolling() > 0) {
264                 NeeoUtil.checkInterrupt();
265                 NeeoUtil.cancel(refreshTask.getAndSet(scheduler.scheduleWithFixedDelay(() -> {
266                     try {
267                         refreshState();
268                     } catch (InterruptedException e) {
269                         logger.debug("Refresh State was interrupted", e);
270                     }
271                 }, 0, config.getRefreshPolling(), TimeUnit.SECONDS)));
272             }
273         } catch (IOException e) {
274             logger.debug("IOException during initialization", e);
275             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
276                     "Room " + config.getRoomKey() + " couldn't be found");
277         } catch (InterruptedException e) {
278             logger.debug("Initialization was interrupted", e);
279             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
280                     "Initialization was interrupted");
281         }
282     }
283
284     /**
285      * Processes the action if it applies to this room
286      *
287      * @param action a non-null action to process
288      */
289     void processAction(NeeoAction action) {
290         Objects.requireNonNull(action, "action cannot be null");
291         final NeeoRoomProtocol protocol = roomProtocol.get();
292         if (protocol != null) {
293             protocol.processAction(action);
294         }
295     }
296
297     /**
298      * Refreshes the state of the room by calling {@link NeeoRoomProtocol#refreshState()}
299      *
300      * @throws InterruptedException if the call is interrupted
301      */
302     private void refreshState() throws InterruptedException {
303         NeeoUtil.checkInterrupt();
304         final NeeoRoomProtocol protocol = roomProtocol.get();
305         if (protocol != null) {
306             NeeoUtil.checkInterrupt();
307             protocol.refreshState();
308         }
309     }
310
311     /**
312      * Helper method to return the {@link NeeoBrainHandler} associated with this handler
313      *
314      * @return a possibly null {@link NeeoBrainHandler}
315      */
316     @Nullable
317     private NeeoBrainHandler getBrainHandler() {
318         final Bridge parent = getBridge();
319         if (parent != null) {
320             final BridgeHandler handler = parent.getHandler();
321             if (handler instanceof NeeoBrainHandler) {
322                 return ((NeeoBrainHandler) handler);
323             }
324         }
325         return null;
326     }
327
328     /**
329      * Returns the {@link NeeoBrainApi} associated with this handler.
330      *
331      * @return the {@link NeeoBrainApi} or null if not found
332      */
333     @Nullable
334     public NeeoBrainApi getNeeoBrainApi() {
335         final NeeoBrainHandler handler = getBrainHandler();
336         return handler == null ? null : handler.getNeeoBrainApi();
337     }
338
339     /**
340      * Returns the brain ID associated with this handler.
341      *
342      * @return the brain ID or null if not found
343      */
344     @Nullable
345     public String getNeeoBrainId() {
346         final NeeoBrainHandler handler = getBrainHandler();
347         return handler == null ? null : handler.getNeeoBrainId();
348     }
349
350     @Override
351     public void dispose() {
352         NeeoUtil.cancel(initializationTask.getAndSet(null));
353         NeeoUtil.cancel(refreshTask.getAndSet(null));
354         roomProtocol.getAndSet(null);
355     }
356 }