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