2 * Copyright (c) 2010-2020 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.neeo.internal.handler;
15 import java.io.IOException;
16 import java.util.HashMap;
18 import java.util.Objects;
19 import java.util.concurrent.Future;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.atomic.AtomicReference;
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;
51 * A subclass of {@link BaseBridgeHandler} that is responsible for handling commands for a room
53 * @author Tim Roberts - Initial contribution
56 public class NeeoRoomHandler extends BaseBridgeHandler {
59 private final Logger logger = LoggerFactory.getLogger(NeeoRoomHandler.class);
62 * The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
64 private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>();
67 * The refresh task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
69 private final AtomicReference<@Nullable Future<?>> refreshTask = new AtomicReference<>();
71 /** The {@link NeeoRoomProtocol} (null until set by {@link #initializationTask}) */
72 private final AtomicReference<@Nullable NeeoRoomProtocol> roomProtocol = new AtomicReference<>();
75 * Instantiates a new neeo room handler.
77 * @param bridge the non-null bridge
79 NeeoRoomHandler(Bridge bridge) {
81 Objects.requireNonNull(bridge, "bridge cannot be null");
85 public void handleCommand(ChannelUID channelUID, Command command) {
86 Objects.requireNonNull(channelUID, "channelUID cannot be null");
87 Objects.requireNonNull(command, "command cannot be null");
89 final NeeoRoomProtocol protocol = roomProtocol.get();
90 if (protocol == null) {
91 logger.debug("Protocol is null - ignoring update: {}", channelUID);
95 final String[] channelIds = UidUtils.parseChannelId(channelUID);
96 if (channelIds.length == 0) {
97 logger.debug("Bad group declaration: {}", channelUID);
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] : "";
106 if (command instanceof RefreshType) {
107 refreshChannel(protocol, groupId, channelKey, channelId);
110 case NeeoConstants.ROOM_GROUP_RECIPE_ID:
112 case NeeoConstants.ROOM_CHANNEL_STATUS:
113 // Ignore OFF status updates
114 if (command == OnOffType.ON) {
115 protocol.startRecipe(channelKey);
120 case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
122 case NeeoConstants.ROOM_CHANNEL_STATUS:
123 if (command instanceof OnOffType) {
124 protocol.setScenarioStatus(channelKey, command == OnOffType.ON);
130 logger.debug("Unknown channel to set: {}", channelUID);
137 * Refresh the specified channel section, key and id using the specified protocol
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
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");
150 case NeeoConstants.ROOM_GROUP_RECIPE_ID:
151 NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
153 case NeeoConstants.ROOM_CHANNEL_NAME:
154 protocol.refreshRecipeName(channelKey);
156 case NeeoConstants.ROOM_CHANNEL_TYPE:
157 protocol.refreshRecipeType(channelKey);
159 case NeeoConstants.ROOM_CHANNEL_ENABLED:
160 protocol.refreshRecipeEnabled(channelKey);
162 case NeeoConstants.ROOM_CHANNEL_STATUS:
163 protocol.refreshRecipeStatus(channelKey);
167 case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
168 NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
170 case NeeoConstants.ROOM_CHANNEL_NAME:
171 protocol.refreshScenarioName(channelKey);
173 case NeeoConstants.ROOM_CHANNEL_CONFIGURED:
174 protocol.refreshScenarioConfigured(channelKey);
176 case NeeoConstants.ROOM_CHANNEL_STATUS:
177 protocol.refreshScenarioStatus(channelKey);
185 public void initialize() {
186 NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
192 * Initializes the task be creating the {@link NeeoRoomProtocol}, going online and then scheduling the refresh task.
194 private void initializeTask() {
195 final NeeoRoomConfig config = getConfigAs(NeeoRoomConfig.class);
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");
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");
212 final NeeoRoom room = brainApi.getRoom(roomKey);
214 final ThingUID thingUid = getThing().getUID();
216 final Map<String, String> properties = new HashMap<>();
217 properties.put("Key", roomKey);
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());
224 NeeoUtil.checkInterrupt();
225 final NeeoRoomProtocol protocol = new NeeoRoomProtocol(new NeeoHandlerCallback() {
228 public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
229 updateStatus(status, detail, msg);
233 public void stateChanged(String channelId, State state) {
234 updateState(channelId, state);
238 public void setProperty(String propertyName, String propertyValue) {
239 getThing().setProperty(propertyName, propertyValue);
243 public void scheduleTask(Runnable task, long milliSeconds) {
244 scheduler.schedule(task, milliSeconds, TimeUnit.MILLISECONDS);
248 public void triggerEvent(String channelID, String event) {
249 triggerChannel(channelID, event);
254 public NeeoBrainApi getApi() {
255 return getNeeoBrainApi();
258 roomProtocol.getAndSet(protocol);
260 NeeoUtil.checkInterrupt();
261 updateStatus(ThingStatus.ONLINE);
263 if (config.getRefreshPolling() > 0) {
264 NeeoUtil.checkInterrupt();
265 NeeoUtil.cancel(refreshTask.getAndSet(scheduler.scheduleWithFixedDelay(() -> {
268 } catch (InterruptedException e) {
269 logger.debug("Refresh State was interrupted", e);
271 }, 0, config.getRefreshPolling(), TimeUnit.SECONDS)));
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");
285 * Processes the action if it applies to this room
287 * @param action a non-null action to process
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);
298 * Refreshes the state of the room by calling {@link NeeoRoomProtocol#refreshState()}
300 * @throws InterruptedException if the call is interrupted
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();
312 * Helper method to return the {@link NeeoBrainHandler} associated with this handler
314 * @return a possibly null {@link NeeoBrainHandler}
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);
329 * Returns the {@link NeeoBrainApi} associated with this handler.
331 * @return the {@link NeeoBrainApi} or null if not found
334 public NeeoBrainApi getNeeoBrainApi() {
335 final NeeoBrainHandler handler = getBrainHandler();
336 return handler == null ? null : handler.getNeeoBrainApi();
340 * Returns the brain ID associated with this handler.
342 * @return the brain ID or null if not found
345 public String getNeeoBrainId() {
346 final NeeoBrainHandler handler = getBrainHandler();
347 return handler == null ? null : handler.getNeeoBrainId();
351 public void dispose() {
352 NeeoUtil.cancel(initializationTask.getAndSet(null));
353 NeeoUtil.cancel(refreshTask.getAndSet(null));
354 roomProtocol.getAndSet(null);