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.nikohomecontrol.internal.handler;
15 import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
16 import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
17 import static org.openhab.core.types.RefreshType.REFRESH;
19 import java.util.HashMap;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
25 import org.openhab.binding.nikohomecontrol.internal.protocol.NhcActionEvent;
26 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
27 import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
28 import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2;
29 import org.openhab.core.library.types.IncreaseDecreaseType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StopMoveType;
33 import org.openhab.core.library.types.UpDownType;
34 import org.openhab.core.thing.Bridge;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.ThingStatusInfo;
40 import org.openhab.core.thing.binding.BaseThingHandler;
41 import org.openhab.core.types.Command;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link NikoHomeControlActionHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author Mark Herwege - Initial Contribution
52 public class NikoHomeControlActionHandler extends BaseThingHandler implements NhcActionEvent {
54 private final Logger logger = LoggerFactory.getLogger(NikoHomeControlActionHandler.class);
56 private volatile @Nullable NhcAction nhcAction;
58 private volatile boolean initialized = false;
60 private String actionId = "";
61 private int stepValue;
62 private boolean invert;
64 public NikoHomeControlActionHandler(Thing thing) {
69 public void handleCommand(ChannelUID channelUID, Command command) {
70 NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
71 if (nhcComm == null) {
72 logger.debug("communication not up yet, cannot handle command {} for {}", command, channelUID);
76 // This can be expensive, therefore do it in a job.
77 scheduler.submit(() -> {
78 if (!nhcComm.communicationActive()) {
79 restartCommunication(nhcComm);
82 if (nhcComm.communicationActive()) {
83 handleCommandSelection(channelUID, command);
88 private void handleCommandSelection(ChannelUID channelUID, Command command) {
89 NhcAction nhcAction = this.nhcAction;
90 if (nhcAction == null) {
91 logger.debug("action with ID {} not initialized", actionId);
95 logger.debug("handle command {} for {}", command, channelUID);
97 if (REFRESH.equals(command)) {
98 actionEvent(nhcAction.getState());
102 switch (channelUID.getId()) {
105 handleSwitchCommand(command);
106 updateStatus(ThingStatus.ONLINE);
108 case CHANNEL_BRIGHTNESS:
109 handleBrightnessCommand(command);
110 updateStatus(ThingStatus.ONLINE);
112 case CHANNEL_ROLLERSHUTTER:
113 handleRollershutterCommand(command);
114 updateStatus(ThingStatus.ONLINE);
117 logger.debug("unexpected command for channel {}", channelUID.getId());
121 private void handleSwitchCommand(Command command) {
122 NhcAction nhcAction = this.nhcAction;
123 if (nhcAction == null) {
124 logger.debug("action with ID {} not initialized", actionId);
128 if (command instanceof OnOffType) {
129 OnOffType s = (OnOffType) command;
130 if (OnOffType.OFF.equals(s)) {
131 nhcAction.execute(NHCOFF);
133 nhcAction.execute(NHCON);
138 private void handleBrightnessCommand(Command command) {
139 NhcAction nhcAction = this.nhcAction;
140 if (nhcAction == null) {
141 logger.debug("action with ID {} not initialized", actionId);
145 if (command instanceof OnOffType) {
146 OnOffType s = (OnOffType) command;
147 if (OnOffType.OFF.equals(s)) {
148 nhcAction.execute(NHCOFF);
150 nhcAction.execute(NHCON);
152 } else if (command instanceof IncreaseDecreaseType) {
153 IncreaseDecreaseType s = (IncreaseDecreaseType) command;
154 int currentValue = nhcAction.getState();
156 if (IncreaseDecreaseType.INCREASE.equals(s)) {
157 newValue = currentValue + stepValue;
158 // round down to step multiple
159 newValue = newValue - newValue % stepValue;
160 nhcAction.execute(Integer.toString(newValue > 100 ? 100 : newValue));
162 newValue = currentValue - stepValue;
163 // round up to step multiple
164 newValue = newValue + newValue % stepValue;
166 nhcAction.execute(NHCOFF);
168 nhcAction.execute(Integer.toString(newValue));
171 } else if (command instanceof PercentType) {
172 PercentType p = (PercentType) command;
173 if (PercentType.ZERO.equals(p)) {
174 nhcAction.execute(NHCOFF);
176 nhcAction.execute(Integer.toString(p.intValue()));
181 private void handleRollershutterCommand(Command command) {
182 NhcAction nhcAction = this.nhcAction;
183 if (nhcAction == null) {
184 logger.debug("action with ID {} not initialized", actionId);
188 if (command instanceof UpDownType) {
189 UpDownType s = (UpDownType) command;
190 if (UpDownType.UP.equals(s)) {
191 nhcAction.execute(!invert ? NHCUP : NHCDOWN);
193 nhcAction.execute(!invert ? NHCDOWN : NHCUP);
195 } else if (command instanceof StopMoveType) {
196 nhcAction.execute(NHCSTOP);
197 } else if (command instanceof PercentType) {
198 PercentType p = (PercentType) command;
199 nhcAction.execute(!invert ? Integer.toString(100 - p.intValue()) : Integer.toString(p.intValue()));
204 public void initialize() {
207 NikoHomeControlActionConfig config;
208 if (thing.getThingTypeUID().equals(THING_TYPE_DIMMABLE_LIGHT)) {
209 config = getConfig().as(NikoHomeControlActionDimmerConfig.class);
210 stepValue = ((NikoHomeControlActionDimmerConfig) config).step;
211 } else if (thing.getThingTypeUID().equals(THING_TYPE_BLIND)) {
212 config = getConfig().as(NikoHomeControlActionBlindConfig.class);
213 invert = ((NikoHomeControlActionBlindConfig) config).invert;
215 config = getConfig().as(NikoHomeControlActionConfig.class);
217 actionId = config.actionId;
219 NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
220 if (bridgeHandler == null) {
221 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
222 "@text/offline.configuration-error.invalid-bridge-handler");
226 updateStatus(ThingStatus.UNKNOWN);
228 Bridge bridge = getBridge();
229 if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
230 // We need to do this in a separate thread because we may have to wait for the
231 // communication to become active
232 scheduler.submit(this::startCommunication);
236 private synchronized void startCommunication() {
237 NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
239 if (nhcComm == null) {
243 if (!nhcComm.communicationActive()) {
244 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
245 "@text/offline.communication-error");
249 NhcAction nhcAction = nhcComm.getActions().get(actionId);
250 if (nhcAction == null) {
251 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
252 "@text/offline.configuration-error.actionId");
256 nhcAction.setEventHandler(this);
258 updateProperties(nhcAction);
260 String actionLocation = nhcAction.getLocation();
261 if (thing.getLocation() == null) {
262 thing.setLocation(actionLocation);
265 this.nhcAction = nhcAction;
267 logger.debug("action initialized {}", actionId);
269 Bridge bridge = getBridge();
270 if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
271 updateStatus(ThingStatus.ONLINE);
273 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
276 actionEvent(nhcAction.getState());
282 public void dispose() {
283 NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
284 if (nhcComm != null) {
285 NhcAction action = nhcComm.getActions().get(actionId);
286 if (action != null) {
287 action.unsetEventHandler();
294 private void updateProperties(NhcAction nhcAction) {
295 Map<String, String> properties = new HashMap<>();
297 properties.put("type", String.valueOf(nhcAction.getType()));
298 if (getThing().getThingTypeUID() == THING_TYPE_BLIND) {
299 properties.put("timeToOpen", String.valueOf(nhcAction.getOpenTime()));
300 properties.put("timeToClose", String.valueOf(nhcAction.getCloseTime()));
303 if (nhcAction instanceof NhcAction2) {
304 NhcAction2 action = (NhcAction2) nhcAction;
305 properties.put(PROPERTY_DEVICE_TYPE, action.getDeviceType());
306 properties.put(PROPERTY_DEVICE_TECHNOLOGY, action.getDeviceTechnology());
307 properties.put(PROPERTY_DEVICE_MODEL, action.getDeviceModel());
310 thing.setProperties(properties);
314 public void actionEvent(int actionState) {
315 NhcAction nhcAction = this.nhcAction;
316 if (nhcAction == null) {
317 logger.debug("action with ID {} not initialized", actionId);
321 ActionType actionType = nhcAction.getType();
323 switch (actionType) {
325 updateState(CHANNEL_BUTTON, (actionState == 0) ? OnOffType.OFF : OnOffType.ON);
326 updateStatus(ThingStatus.ONLINE);
328 updateState(CHANNEL_SWITCH, (actionState == 0) ? OnOffType.OFF : OnOffType.ON);
329 updateStatus(ThingStatus.ONLINE);
332 updateState(CHANNEL_BRIGHTNESS, new PercentType(actionState));
333 updateStatus(ThingStatus.ONLINE);
336 updateState(CHANNEL_ROLLERSHUTTER,
337 !invert ? new PercentType(100 - actionState) : new PercentType(actionState));
338 updateStatus(ThingStatus.ONLINE);
341 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
342 "@text/offline.configuration-error.actionType");
347 public void actionInitialized() {
348 Bridge bridge = getBridge();
349 if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
350 updateStatus(ThingStatus.ONLINE);
355 public void actionRemoved() {
356 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
357 "@text/offline.configuration-error.actionRemoved");
360 private void restartCommunication(NikoHomeControlCommunication nhcComm) {
361 // We lost connection but the connection object is there, so was correctly started.
362 // Try to restart communication.
363 nhcComm.scheduleRestartCommunication();
364 // If still not active, take thing offline and return.
365 if (!nhcComm.communicationActive()) {
366 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
367 "@text/offline.communication-error");
370 // Also put the bridge back online
371 NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
372 if (nhcBridgeHandler != null) {
373 nhcBridgeHandler.bridgeOnline();
375 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
376 "@text/offline.configuration-error.invalid-bridge-handler");
380 private @Nullable NikoHomeControlCommunication getCommunication(
381 @Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
382 return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
385 private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
386 Bridge nhcBridge = getBridge();
387 return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
391 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
392 ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
393 if (ThingStatus.ONLINE.equals(bridgeStatus)) {
395 scheduler.submit(this::startCommunication);
397 updateStatus(ThingStatus.ONLINE);
400 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);