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.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.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;
50 * A subclass of {@link BaseBridgeHandler} that is responsible for handling commands for a room
52 * @author Tim Roberts - Initial contribution
55 public class NeeoRoomHandler extends BaseBridgeHandler {
58 private final Logger logger = LoggerFactory.getLogger(NeeoRoomHandler.class);
61 * The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
63 private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>();
66 * The refresh task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
68 private final AtomicReference<@Nullable Future<?>> refreshTask = new AtomicReference<>();
70 /** The {@link NeeoRoomProtocol} (null until set by {@link #initializationTask}) */
71 private final AtomicReference<@Nullable NeeoRoomProtocol> roomProtocol = new AtomicReference<>();
74 * Instantiates a new neeo room handler.
76 * @param bridge the non-null bridge
78 NeeoRoomHandler(Bridge bridge) {
80 Objects.requireNonNull(bridge, "bridge cannot be null");
84 public void handleCommand(ChannelUID channelUID, Command command) {
85 Objects.requireNonNull(channelUID, "channelUID cannot be null");
86 Objects.requireNonNull(command, "command cannot be null");
88 final NeeoRoomProtocol protocol = roomProtocol.get();
89 if (protocol == null) {
90 logger.debug("Protocol is null - ignoring update: {}", channelUID);
94 final String[] channelIds = UidUtils.parseChannelId(channelUID);
95 if (channelIds.length == 0) {
96 logger.debug("Bad group declaration: {}", channelUID);
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] : "";
105 if (command instanceof RefreshType) {
106 refreshChannel(protocol, groupId, channelKey, channelId);
109 case NeeoConstants.ROOM_GROUP_RECIPE_ID:
111 case NeeoConstants.ROOM_CHANNEL_STATUS:
112 // Ignore OFF status updates
113 if (command == OnOffType.ON) {
114 protocol.startRecipe(channelKey);
119 case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
121 case NeeoConstants.ROOM_CHANNEL_STATUS:
122 if (command instanceof OnOffType) {
123 protocol.setScenarioStatus(channelKey, command == OnOffType.ON);
129 logger.debug("Unknown channel to set: {}", channelUID);
136 * Refresh the specified channel section, key and id using the specified protocol
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
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");
149 case NeeoConstants.ROOM_GROUP_RECIPE_ID:
150 NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
152 case NeeoConstants.ROOM_CHANNEL_NAME:
153 protocol.refreshRecipeName(channelKey);
155 case NeeoConstants.ROOM_CHANNEL_TYPE:
156 protocol.refreshRecipeType(channelKey);
158 case NeeoConstants.ROOM_CHANNEL_ENABLED:
159 protocol.refreshRecipeEnabled(channelKey);
161 case NeeoConstants.ROOM_CHANNEL_STATUS:
162 protocol.refreshRecipeStatus(channelKey);
166 case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
167 NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
169 case NeeoConstants.ROOM_CHANNEL_NAME:
170 protocol.refreshScenarioName(channelKey);
172 case NeeoConstants.ROOM_CHANNEL_CONFIGURED:
173 protocol.refreshScenarioConfigured(channelKey);
175 case NeeoConstants.ROOM_CHANNEL_STATUS:
176 protocol.refreshScenarioStatus(channelKey);
184 public void initialize() {
185 NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
191 * Initializes the task be creating the {@link NeeoRoomProtocol}, going online and then scheduling the refresh task.
193 private void initializeTask() {
194 final NeeoRoomConfig config = getConfigAs(NeeoRoomConfig.class);
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");
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");
211 final NeeoRoom room = brainApi.getRoom(roomKey);
213 final ThingUID thingUid = getThing().getUID();
215 final Map<String, String> properties = new HashMap<>();
216 properties.put("Key", roomKey);
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());
223 NeeoUtil.checkInterrupt();
224 final NeeoRoomProtocol protocol = new NeeoRoomProtocol(new NeeoHandlerCallback() {
227 public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
228 updateStatus(status, detail, msg);
232 public void stateChanged(String channelId, State state) {
233 updateState(channelId, state);
237 public void setProperty(String propertyName, String propertyValue) {
238 getThing().setProperty(propertyName, propertyValue);
242 public void scheduleTask(Runnable task, long milliSeconds) {
243 scheduler.schedule(task, milliSeconds, TimeUnit.MILLISECONDS);
247 public void triggerEvent(String channelID, String event) {
248 triggerChannel(channelID, event);
253 public NeeoBrainApi getApi() {
254 return getNeeoBrainApi();
257 roomProtocol.getAndSet(protocol);
259 NeeoUtil.checkInterrupt();
260 updateStatus(ThingStatus.ONLINE);
262 if (config.getRefreshPolling() > 0) {
263 NeeoUtil.checkInterrupt();
264 NeeoUtil.cancel(refreshTask.getAndSet(scheduler.scheduleWithFixedDelay(() -> {
267 } catch (InterruptedException e) {
268 logger.debug("Refresh State was interrupted", e);
270 }, 0, config.getRefreshPolling(), TimeUnit.SECONDS)));
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");
284 * Processes the action if it applies to this room
286 * @param action a non-null action to process
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);
297 * Refreshes the state of the room by calling {@link NeeoRoomProtocol#refreshState()}
299 * @throws InterruptedException if the call is interrupted
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();
311 * Helper method to return the {@link NeeoBrainHandler} associated with this handler
313 * @return a possibly null {@link NeeoBrainHandler}
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);
328 * Returns the {@link NeeoBrainApi} associated with this handler.
330 * @return the {@link NeeoBrainApi} or null if not found
333 public NeeoBrainApi getNeeoBrainApi() {
334 final NeeoBrainHandler handler = getBrainHandler();
335 return handler == null ? null : handler.getNeeoBrainApi();
339 * Returns the brain ID associated with this handler.
341 * @return the brain ID or null if not found
344 public String getNeeoBrainId() {
345 final NeeoBrainHandler handler = getBrainHandler();
346 return handler == null ? null : handler.getNeeoBrainId();
350 public void dispose() {
351 NeeoUtil.cancel(initializationTask.getAndSet(null));
352 NeeoUtil.cancel(refreshTask.getAndSet(null));
353 roomProtocol.getAndSet(null);