added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.nuki-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-nuki" description="Nuki Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-http</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.nuki/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link NukiBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Markus Katter - Initial contribution
* @contributer Christian Hoefler - Door sensor integration
*/
public class NukiBindingConstants {
public static final String BINDING_ID = "nuki";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID THING_TYPE_SMARTLOCK = new ThingTypeUID(BINDING_ID, "smartlock");
public static final Set<ThingTypeUID> THING_TYPE_BRIDGE_UIDS = Collections.singleton(THING_TYPE_BRIDGE);
public static final Set<ThingTypeUID> THING_TYPE_SMARTLOCK_UIDS = Collections.singleton(THING_TYPE_SMARTLOCK);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.concat(THING_TYPE_BRIDGE_UIDS.stream(), THING_TYPE_SMARTLOCK_UIDS.stream()).collect(Collectors.toSet());
// List of all Channel ids
public static final String CHANNEL_SMARTLOCK_LOCK = "lock";
public static final String CHANNEL_SMARTLOCK_STATE = "lockState";
public static final String CHANNEL_SMARTLOCK_LOW_BATTERY = "lowBattery";
public static final String CHANNEL_SMARTLOCK_DOOR_STATE = "doorsensorState";
// List of all config-description parameters
public static final String CONFIG_IP = "ip";
public static final String CONFIG_PORT = "port";
public static final String CONFIG_MANAGECB = "manageCallbacks";
public static final String CONFIG_API_TOKEN = "apiToken";
public static final String CONFIG_NUKI_ID = "nukiId";
public static final String CONFIG_UNLATCH = "unlatch";
// Nuki Bridge API REST Endpoints
public static final String URI_INFO = "http://%s:%s/info?token=%s";
public static final String URI_LOCKSTATE = "http://%s:%s/lockState?token=%s&nukiId=%s";
public static final String URI_LOCKACTION = "http://%s:%s/lockAction?token=%s&nukiId=%s&action=%s";
public static final String URI_CBADD = "http://%s:%s/callback/add?token=%s&url=%s";
public static final String URI_CBLIST = "http://%s:%s/callback/list?token=%s";
public static final String URI_CBREMOVE = "http://%s:%s/callback/remove?token=%s&id=%s";
// openHAB Callback Endpoint & Nuki Bridge Callback URL
public static final String CALLBACK_ENDPOINT = "/nuki/bcb";
public static final String CALLBACK_URL = "http://%s" + CALLBACK_ENDPOINT;
// Nuki Bridge API Lock Actions
public static final int LOCK_ACTIONS_UNLOCK = 1;
public static final int LOCK_ACTIONS_LOCK = 2;
public static final int LOCK_ACTIONS_UNLATCH = 3;
public static final int LOCK_ACTIONS_LOCKNGO_UNLOCK = 4;
public static final int LOCK_ACTIONS_LOCKNGO_UNLATCH = 5;
// Nuki Bridge API Lock States
public static final int LOCK_STATES_UNCALIBRATED = 0;
public static final int LOCK_STATES_LOCKED = 1;
public static final int LOCK_STATES_UNLOCKING = 2;
public static final int LOCK_STATES_UNLOCKED = 3;
public static final int LOCK_STATES_LOCKING = 4;
public static final int LOCK_STATES_UNLATCHED = 5;
public static final int LOCK_STATES_UNLOCKED_LOCKNGO = 6;
public static final int LOCK_STATES_UNLATCHING = 7;
public static final int LOCK_STATES_MOTOR_BLOCKED = 254;
public static final int LOCK_STATES_UNDEFINED = 255;
// Nuki Binding additional Lock States
public static final int LOCK_STATES_UNLOCKING_LOCKNGO = 1002;
public static final int LOCK_STATES_UNLATCHING_LOCKNGO = 1007;
// Nuki Binding Door States
public static final int DOORSENSOR_STATES_UNAVAILABLE = 0;
public static final int DOORSENSOR_STATES_DEACTIVATED = 1;
public static final int DOORSENSOR_STATES_CLOSED = 2;
public static final int DOORSENSOR_STATES_OPEN = 3;
public static final int DOORSENSOR_STATES_UNKNOWN = 4;
public static final int DOORSENSOR_STATES_CALIBRATING = 5;
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal;
import java.util.ArrayList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.nuki.internal.dataexchange.NukiApiServlet;
import org.openhab.binding.nuki.internal.handler.NukiBridgeHandler;
import org.openhab.binding.nuki.internal.handler.NukiSmartLockHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.net.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NukiHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Markus Katter - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nuki")
@NonNullByDefault
public class NukiHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(NukiHandlerFactory.class);
private final HttpService httpService;
private final HttpClient httpClient;
private final NetworkAddressService networkAddressService;
private @Nullable String callbackUrl;
private @Nullable NukiApiServlet nukiApiServlet;
@Activate
public NukiHandlerFactory(@Reference HttpService httpService, @Reference final HttpClientFactory httpClientFactory,
@Reference NetworkAddressService networkAddressService) {
this.httpService = httpService;
this.httpClient = httpClientFactory.getCommonHttpClient();
this.networkAddressService = networkAddressService;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return NukiBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
logger.debug("NukiHandlerFactory:createHandler({})", thing);
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (NukiBindingConstants.THING_TYPE_BRIDGE_UIDS.contains(thingTypeUID)) {
callbackUrl = createCallbackUrl();
NukiBridgeHandler nukiBridgeHandler = new NukiBridgeHandler((Bridge) thing, httpClient, callbackUrl);
if (!nukiBridgeHandler.isInitializable()) {
return null;
}
if (nukiApiServlet == null) {
nukiApiServlet = new NukiApiServlet(httpService);
}
nukiApiServlet.add(nukiBridgeHandler);
return nukiBridgeHandler;
} else if (NukiBindingConstants.THING_TYPE_SMARTLOCK_UIDS.contains(thingTypeUID)) {
return new NukiSmartLockHandler(thing);
}
logger.trace("No valid Handler found for Thing[{}]!", thingTypeUID);
return null;
}
@Override
public void unregisterHandler(Thing thing) {
super.unregisterHandler(thing);
logger.trace("NukiHandlerFactory:unregisterHandler({})", thing);
if (thing.getHandler() instanceof NukiBridgeHandler && nukiApiServlet != null) {
nukiApiServlet.remove((NukiBridgeHandler) thing.getHandler());
if (nukiApiServlet.countNukiBridgeHandlers() == 0) {
nukiApiServlet = null;
}
}
}
private @Nullable String createCallbackUrl() {
logger.trace("createCallbackUrl()");
if (callbackUrl != null) {
return callbackUrl;
}
final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
if (ipAddress == null) {
logger.warn("No network interface could be found.");
return null;
}
// we do not use SSL as it can cause certificate validation issues.
final int port = HttpServiceUtil.getHttpServicePort(bundleContext);
if (port == -1) {
logger.warn("Cannot find port of the http service.");
return null;
}
ArrayList<String> parameters = new ArrayList<>();
parameters.add(ipAddress + ":" + port);
String callbackUrl = String.format(NukiBindingConstants.CALLBACK_URL, parameters.toArray());
logger.trace("callbackUrl[{}]", callbackUrl);
return callbackUrl;
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.converter;
import java.util.HashMap;
import java.util.Map;
import org.openhab.binding.nuki.internal.NukiBindingConstants;
/**
* The {@link LockActionConverter} is responsible for mapping Binding Lock States to Bridge HTTP-API Lock Actions.
*
* @author Markus Katter - Initial contribution
*/
public abstract class LockActionConverter {
private static Map<Integer, Integer> mapping;
private static void setupMapping() {
mapping = new HashMap<>();
mapping.put(NukiBindingConstants.LOCK_STATES_UNLOCKING, NukiBindingConstants.LOCK_ACTIONS_UNLOCK);
mapping.put(NukiBindingConstants.LOCK_STATES_LOCKING, NukiBindingConstants.LOCK_ACTIONS_LOCK);
mapping.put(NukiBindingConstants.LOCK_STATES_UNLATCHING, NukiBindingConstants.LOCK_ACTIONS_UNLATCH);
mapping.put(NukiBindingConstants.LOCK_STATES_UNLOCKING_LOCKNGO,
NukiBindingConstants.LOCK_ACTIONS_LOCKNGO_UNLOCK);
mapping.put(NukiBindingConstants.LOCK_STATES_UNLATCHING_LOCKNGO,
NukiBindingConstants.LOCK_ACTIONS_LOCKNGO_UNLATCH);
}
public static int getLockActionFor(int lockState) {
if (mapping == null) {
setupMapping();
}
return mapping.get(lockState);
}
public static int getLockStateFor(int lockAction) {
if (mapping == null) {
setupMapping();
}
for (Map.Entry<Integer, Integer> entry : mapping.entrySet()) {
if (entry.getValue() == lockAction) {
return entry.getKey();
}
}
return 0;
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import org.openhab.binding.nuki.internal.dto.BridgeApiCallbackAddDto;
/**
* The {@link BridgeCallbackAddResponse} class wraps {@link BridgeApiCallbackAddDto} class.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeCallbackAddResponse extends NukiBaseResponse {
public BridgeCallbackAddResponse(int status, String message, BridgeApiCallbackAddDto bridgeApiCallbackAddDto) {
super(status, message);
if (bridgeApiCallbackAddDto != null) {
this.setSuccess(bridgeApiCallbackAddDto.isSuccess());
if (bridgeApiCallbackAddDto.getMessage() != null) {
this.setMessage(bridgeApiCallbackAddDto.getMessage());
}
}
}
public BridgeCallbackAddResponse(NukiBaseResponse nukiBaseResponse) {
super(nukiBaseResponse.getStatus(), nukiBaseResponse.getMessage());
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import java.util.List;
import org.openhab.binding.nuki.internal.dto.BridgeApiCallbackListCallbackDto;
import org.openhab.binding.nuki.internal.dto.BridgeApiCallbackListDto;
/**
* The {@link BridgeCallbackListResponse} class wraps {@link BridgeApiCallbackListDto} class.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeCallbackListResponse extends NukiBaseResponse {
private List<BridgeApiCallbackListCallbackDto> callbacks;
public BridgeCallbackListResponse(int status, String message, BridgeApiCallbackListDto bridgeApiCallbackListDto) {
super(status, message);
if (bridgeApiCallbackListDto != null) {
this.setSuccess(true);
this.callbacks = bridgeApiCallbackListDto.getCallbacks();
}
}
public BridgeCallbackListResponse(NukiBaseResponse nukiBaseResponse) {
super(nukiBaseResponse.getStatus(), nukiBaseResponse.getMessage());
}
public List<BridgeApiCallbackListCallbackDto> getCallbacks() {
return callbacks;
}
public void setCallbacks(List<BridgeApiCallbackListCallbackDto> callbacks) {
this.callbacks = callbacks;
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import org.openhab.binding.nuki.internal.dto.BridgeApiCallbackRemoveDto;
/**
* The {@link BridgeCallbackRemoveResponse} class wraps {@link BridgeApiCallbackRemoveDto} class.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeCallbackRemoveResponse extends NukiBaseResponse {
public BridgeCallbackRemoveResponse(int status, String message,
BridgeApiCallbackRemoveDto bridgeApiCallbackRemoveDto) {
super(status, message);
if (bridgeApiCallbackRemoveDto != null) {
this.setSuccess(bridgeApiCallbackRemoveDto.isSuccess());
if (bridgeApiCallbackRemoveDto.getMessage() != null) {
this.setMessage(bridgeApiCallbackRemoveDto.getMessage());
}
}
}
public BridgeCallbackRemoveResponse(NukiBaseResponse nukiBaseResponse) {
super(nukiBaseResponse.getStatus(), nukiBaseResponse.getMessage());
}
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import java.util.List;
import org.openhab.binding.nuki.internal.dto.BridgeApiInfoDto;
import org.openhab.binding.nuki.internal.dto.BridgeApiInfoScanResultDto;
/**
* The {@link BridgeInfoResponse} class wraps {@link BridgeApiInfoDto} class.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeInfoResponse extends NukiBaseResponse {
private int bridgeType;
private int hardwareId;
private int serverId;
private String firmwareVersion;
private String wifiFirmwareVersion;
private int uptime;
private String currentTime;
private boolean serverConnected;
private List<BridgeApiInfoScanResultDto> scanResults;
public BridgeInfoResponse(int status, String message, BridgeApiInfoDto bridgeApiInfoDto) {
super(status, message);
if (bridgeApiInfoDto != null) {
this.setSuccess(true);
this.setBridgeType(bridgeApiInfoDto.getBridgeType());
this.setHardwareId(bridgeApiInfoDto.getIds().getHardwareId());
this.setServerId(bridgeApiInfoDto.getIds().getServerId());
this.setFirmwareVersion(bridgeApiInfoDto.getVersions().getFirmwareVersion());
this.setWifiFirmwareVersion(bridgeApiInfoDto.getVersions().getWifiFirmwareVersion());
this.setUptime(bridgeApiInfoDto.getUptime());
this.setCurrentTime(bridgeApiInfoDto.getCurrentTime());
this.setScanResults(bridgeApiInfoDto.getScanResults());
}
}
public BridgeInfoResponse(NukiBaseResponse nukiBaseResponse) {
super(nukiBaseResponse.getStatus(), nukiBaseResponse.getMessage());
}
public int getBridgeType() {
return bridgeType;
}
public void setBridgeType(int bridgeType) {
this.bridgeType = bridgeType;
}
public int getHardwareId() {
return hardwareId;
}
public void setHardwareId(int hardwareId) {
this.hardwareId = hardwareId;
}
public int getServerId() {
return serverId;
}
public void setServerId(int serverId) {
this.serverId = serverId;
}
public String getFirmwareVersion() {
return firmwareVersion;
}
public void setFirmwareVersion(String firmwareVersion) {
this.firmwareVersion = firmwareVersion;
}
public String getWifiFirmwareVersion() {
return wifiFirmwareVersion;
}
public void setWifiFirmwareVersion(String wifiFirmwareVersion) {
this.wifiFirmwareVersion = wifiFirmwareVersion;
}
public int getUptime() {
return uptime;
}
public void setUptime(int uptime) {
this.uptime = uptime;
}
public String getCurrentTime() {
return currentTime;
}
public void setCurrentTime(String currentTime) {
this.currentTime = currentTime;
}
public boolean isServerConnected() {
return serverConnected;
}
public void setServerConnected(boolean serverConnected) {
this.serverConnected = serverConnected;
}
public List<BridgeApiInfoScanResultDto> getScanResults() {
return scanResults;
}
public void setScanResults(List<BridgeApiInfoScanResultDto> scanResults) {
this.scanResults = scanResults;
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import org.openhab.binding.nuki.internal.dto.BridgeApiLockActionDto;
/**
* The {@link BridgeLockActionResponse} class wraps {@link BridgeApiLockActionDto} class.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeLockActionResponse extends NukiBaseResponse {
private boolean batteryCritical;
public BridgeLockActionResponse(int status, String message, BridgeApiLockActionDto bridgeApiLockActionDto) {
super(status, message);
if (bridgeApiLockActionDto != null) {
this.setSuccess(bridgeApiLockActionDto.isSuccess());
this.setBatteryCritical(bridgeApiLockActionDto.isBatteryCritical());
}
}
public BridgeLockActionResponse(NukiBaseResponse nukiBaseResponse) {
super(nukiBaseResponse.getStatus(), nukiBaseResponse.getMessage());
}
public boolean isBatteryCritical() {
return batteryCritical;
}
public void setBatteryCritical(boolean batteryCritical) {
this.batteryCritical = batteryCritical;
}
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import org.openhab.binding.nuki.internal.dto.BridgeApiLockStateDto;
/**
* The {@link BridgeLockStateResponse} class wraps {@link BridgeApiLockStateDto} class.
*
* @author Markus Katter - Initial contribution
* @contributer Christian Hoefler - Door sensor integration
*/
public class BridgeLockStateResponse extends NukiBaseResponse {
private int state;
private String stateName;
private boolean batteryCritical;
private int doorsensorState;
public BridgeLockStateResponse(int status, String message, BridgeApiLockStateDto bridgeApiLockStateDto) {
super(status, message);
if (bridgeApiLockStateDto != null) {
this.setSuccess(bridgeApiLockStateDto.isSuccess());
this.setState(bridgeApiLockStateDto.getState());
this.setStateName(bridgeApiLockStateDto.getStateName());
this.setDoorsensorState(bridgeApiLockStateDto.getDoorsensorState());
this.setBatteryCritical(bridgeApiLockStateDto.isBatteryCritical());
}
}
public BridgeLockStateResponse(NukiBaseResponse nukiBaseResponse) {
super(nukiBaseResponse.getStatus(), nukiBaseResponse.getMessage());
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
public boolean isBatteryCritical() {
return batteryCritical;
}
public void setBatteryCritical(boolean batteryCritical) {
this.batteryCritical = batteryCritical;
}
public int getDoorsensorState() {
return doorsensorState;
}
public void setDoorsensorState(int doorsensorState) {
this.doorsensorState = doorsensorState;
}
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.nuki.internal.NukiBindingConstants;
import org.openhab.binding.nuki.internal.dto.BridgeApiLockStateRequestDto;
import org.openhab.binding.nuki.internal.dto.NukiHttpServerStatusResponseDto;
import org.openhab.binding.nuki.internal.handler.NukiBridgeHandler;
import org.openhab.binding.nuki.internal.handler.NukiSmartLockHandler;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.State;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link NukiApiServlet} class is responsible for handling the callbacks from the Nuki Bridge.
*
* @author Markus Katter - Initial contribution
* @contributer Christian Hoefler - Door sensor integration
*/
public class NukiApiServlet extends HttpServlet {
private final Logger logger = LoggerFactory.getLogger(NukiApiServlet.class);
private static final long serialVersionUID = -3601163473320027239L;
private static final String CHARSET = "utf-8";
private static final String APPLICATION_JSON = "application/json";
private HttpService httpService;
private List<NukiBridgeHandler> nukiBridgeHandlers = new ArrayList<>();
private String path;
private Gson gson;
public NukiApiServlet(HttpService httpService) {
logger.debug("Instantiating NukiApiServlet({})", httpService);
this.httpService = httpService;
gson = new Gson();
}
public void add(@NonNull NukiBridgeHandler nukiBridgeHandler) {
logger.trace("Adding NukiBridgeHandler[{}] for Bridge[{}] to NukiApiServlet.",
nukiBridgeHandler.getThing().getUID(),
nukiBridgeHandler.getThing().getConfiguration().get(NukiBindingConstants.CONFIG_IP));
if (!nukiBridgeHandler.isInitializable()) {
logger.debug("NukiBridgeHandler[{}] is not initializable, check required configuration!",
nukiBridgeHandler.getThing().getUID());
return;
}
if (nukiBridgeHandlers.isEmpty()) {
this.activate();
}
nukiBridgeHandlers.add(nukiBridgeHandler);
}
public void remove(NukiBridgeHandler nukiBridgeHandler) {
logger.trace("Removing NukiBridgeHandler[{}] for Bridge[{}] from NukiApiServlet.",
nukiBridgeHandler.getThing().getUID(),
nukiBridgeHandler.getThing().getConfiguration().get(NukiBindingConstants.CONFIG_IP));
nukiBridgeHandlers.remove(nukiBridgeHandler);
if (nukiBridgeHandlers.isEmpty()) {
this.deactivate();
}
}
public int countNukiBridgeHandlers() {
return nukiBridgeHandlers.size();
}
private void activate() {
logger.debug("Activating NukiApiServlet.");
path = NukiBindingConstants.CALLBACK_ENDPOINT;
Dictionary<String, String> servletParams = new Hashtable<>();
try {
httpService.registerServlet(path, this, servletParams, httpService.createDefaultHttpContext());
logger.debug("Started NukiApiServlet at path[{}]", path);
} catch (ServletException | NamespaceException e) {
logger.error("ERROR: {}", e.getMessage(), e);
}
}
private void deactivate() {
logger.trace("deactivate()");
httpService.unregister(path);
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
logger.debug("Servlet Request at URI[{}] request[{}]", request.getRequestURI(), request);
BridgeApiLockStateRequestDto bridgeApiLockStateRequestDto = getBridgeApiLockStateRequestDto(request);
if (bridgeApiLockStateRequestDto == null) {
logger.error("Could not handle Bridge CallBack Request - Discarding!");
logger.error("Please report a bug, if this request was done by the Nuki Bridge!");
setHeaders(response);
response.setStatus(HttpStatus.BAD_REQUEST_400);
response.getWriter().println(gson.toJson(new NukiHttpServerStatusResponseDto("Invalid BCB-Request!")));
return;
}
String nukiId = String.format("%08X", (bridgeApiLockStateRequestDto.getNukiId()));
String nukiIdThing;
for (NukiBridgeHandler nukiBridgeHandler : nukiBridgeHandlers) {
logger.trace("Searching Bridge[{}] with NukiBridgeHandler[{}] for nukiId[{}].",
nukiBridgeHandler.getThing().getConfiguration().get(NukiBindingConstants.CONFIG_IP),
nukiBridgeHandler.getThing().getUID(), nukiId);
List<@NonNull Thing> allSmartLocks = nukiBridgeHandler.getThing().getThings();
for (Thing thing : allSmartLocks) {
nukiIdThing = thing.getConfiguration().containsKey(NukiBindingConstants.CONFIG_NUKI_ID)
? (String) thing.getConfiguration().get(NukiBindingConstants.CONFIG_NUKI_ID)
: null;
if (nukiIdThing != null && nukiIdThing.equals(nukiId)) {
logger.debug("Processing ThingUID[{}] - nukiId[{}]", thing.getUID(), nukiId);
NukiSmartLockHandler nsh = getSmartLockHandler(thing);
if (nsh == null) {
logger.debug("Could not update channels for ThingUID[{}] because Handler is null!",
thing.getUID());
break;
}
Channel channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK);
if (channel != null) {
State state = bridgeApiLockStateRequestDto.getState() == NukiBindingConstants.LOCK_STATES_LOCKED
? OnOffType.ON
: OnOffType.OFF;
nsh.handleApiServletUpdate(channel.getUID(), state);
}
channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_STATE);
if (channel != null) {
State state = new DecimalType(bridgeApiLockStateRequestDto.getState());
nsh.handleApiServletUpdate(channel.getUID(), state);
}
channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_LOW_BATTERY);
if (channel != null) {
State state = bridgeApiLockStateRequestDto.isBatteryCritical() ? OnOffType.ON : OnOffType.OFF;
nsh.handleApiServletUpdate(channel.getUID(), state);
}
channel = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_DOOR_STATE);
if (channel != null) {
State state = new DecimalType(bridgeApiLockStateRequestDto.getDoorsensorState());
nsh.handleApiServletUpdate(channel.getUID(), state);
}
setHeaders(response);
response.getWriter().println(gson.toJson(new NukiHttpServerStatusResponseDto("OK")));
return;
}
}
}
logger.debug("Smart Lock with nukiId[{}] not found!", nukiId);
setHeaders(response);
response.setStatus(HttpStatus.NOT_FOUND_404);
response.getWriter().println(gson.toJson(new NukiHttpServerStatusResponseDto("Smart Lock not found!")));
}
private BridgeApiLockStateRequestDto getBridgeApiLockStateRequestDto(HttpServletRequest request) {
logger.trace("getBridgeApiLockStateRequestDto(...)");
String requestContent = null;
try {
requestContent = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
BridgeApiLockStateRequestDto bridgeApiLockStateRequestDto = gson.fromJson(requestContent,
BridgeApiLockStateRequestDto.class);
if (bridgeApiLockStateRequestDto.getNukiId() != 0) {
logger.trace("requestContent[{}]", requestContent);
return bridgeApiLockStateRequestDto;
} else {
logger.error("Invalid BCB-Request payload data!");
logger.error("requestContent[{}]", requestContent);
}
} catch (IOException e) {
logger.error("Could not read payload from BCB-Request! Message[{}]", e.getMessage());
} catch (Exception e) {
logger.error("Could not create BridgeApiLockStateRequestDto from BCB-Request! Message[{}]", e.getMessage());
logger.error("requestContent[{}]", requestContent);
}
return null;
}
private NukiSmartLockHandler getSmartLockHandler(Thing thing) {
logger.trace("getSmartLockHandler(...) from thing[{}]", thing.getUID());
NukiSmartLockHandler nsh = (NukiSmartLockHandler) thing.getHandler();
if (nsh == null) {
logger.debug("Could not get NukiSmartLockHandler for ThingUID[{}]!", thing.getUID());
return null;
}
return nsh;
}
private void setHeaders(HttpServletResponse response) {
response.setCharacterEncoding(CHARSET);
response.setContentType(APPLICATION_JSON);
}
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import java.time.Instant;
/**
* The {@link NukiBaseResponse} class is the base class for API Responses.
*
* @author Markus Katter - Initial contribution
*/
public class NukiBaseResponse {
private int status;
private String message;
private boolean success;
private Instant created;
public NukiBaseResponse(int status, String message) {
this.status = status;
this.message = message;
this.created = Instant.now();
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public Instant getCreated() {
return this.created;
}
}

View File

@@ -0,0 +1,264 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dataexchange;
import java.io.InterruptedIOException;
import java.math.BigDecimal;
import java.net.SocketException;
import java.net.URLEncoder;
import java.time.Instant;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpResponseException;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.nuki.internal.NukiBindingConstants;
import org.openhab.binding.nuki.internal.dto.BridgeApiCallbackAddDto;
import org.openhab.binding.nuki.internal.dto.BridgeApiCallbackListDto;
import org.openhab.binding.nuki.internal.dto.BridgeApiCallbackRemoveDto;
import org.openhab.binding.nuki.internal.dto.BridgeApiInfoDto;
import org.openhab.binding.nuki.internal.dto.BridgeApiLockActionDto;
import org.openhab.binding.nuki.internal.dto.BridgeApiLockStateDto;
import org.openhab.core.config.core.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link NukiHttpClient} class is responsible for getting data from the Nuki Bridge.
*
* @author Markus Katter - Initial contribution
*/
public class NukiHttpClient {
private final Logger logger = LoggerFactory.getLogger(NukiHttpClient.class);
private static final long CACHE_PERIOD = 5;
private HttpClient httpClient;
private Configuration configuration;
private Gson gson;
private BridgeLockStateResponse bridgeLockStateResponseCache;
public NukiHttpClient(HttpClient httpClient, Configuration configuration) {
logger.debug("Instantiating NukiHttpClient({})", configuration);
this.configuration = configuration;
this.httpClient = httpClient;
gson = new Gson();
}
private String prepareUri(String uriTemplate, String... additionalArguments) {
String configIp = (String) configuration.get(NukiBindingConstants.CONFIG_IP);
BigDecimal configPort = (BigDecimal) configuration.get(NukiBindingConstants.CONFIG_PORT);
String configApiToken = (String) configuration.get(NukiBindingConstants.CONFIG_API_TOKEN);
ArrayList<String> parameters = new ArrayList<>();
parameters.add(configIp);
parameters.add(configPort.toString());
parameters.add(configApiToken);
if (additionalArguments != null) {
for (String argument : additionalArguments) {
parameters.add(argument);
}
}
String uri = String.format(uriTemplate, parameters.toArray());
logger.trace("prepareUri(...):URI[{}]", uri);
return uri;
}
private synchronized ContentResponse executeRequest(String uri)
throws InterruptedException, ExecutionException, TimeoutException {
logger.debug("executeRequest({})", uri);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
ContentResponse contentResponse = httpClient.GET(uri);
logger.debug("contentResponseAsString[{}]", contentResponse.getContentAsString());
return contentResponse;
}
private NukiBaseResponse handleException(Exception e) {
if (e instanceof ExecutionException) {
if (e.getCause() instanceof HttpResponseException) {
HttpResponseException cause = (HttpResponseException) e.getCause();
int status = cause.getResponse().getStatus();
String reason = cause.getResponse().getReason();
logger.debug("HTTP Response Exception! Status[{}] - Reason[{}]! Check your API Token!", status, reason);
return new NukiBaseResponse(status, reason);
} else if (e.getCause() instanceof InterruptedIOException) {
logger.debug(
"InterruptedIOException! Exception[{}]! Check IP/Port configuration and if Nuki Bridge is powered on!",
e.getMessage());
return new NukiBaseResponse(HttpStatus.REQUEST_TIMEOUT_408,
"InterruptedIOException! Check IP/Port configuration and if Nuki Bridge is powered on!");
} else if (e.getCause() instanceof SocketException) {
logger.debug(
"SocketException! Exception[{}]! Check IP/Port configuration and if Nuki Bridge is powered on!",
e.getMessage());
return new NukiBaseResponse(HttpStatus.NOT_FOUND_404,
"SocketException! Check IP/Port configuration and if Nuki Bridge is powered on!");
}
}
logger.error("Could not handle Exception! Exception[{}]", e.getMessage(), e);
return new NukiBaseResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, e.getMessage());
}
public BridgeInfoResponse getBridgeInfo() {
logger.debug("getBridgeInfo() in thread {}", Thread.currentThread().getId());
String uri = prepareUri(NukiBindingConstants.URI_INFO);
try {
ContentResponse contentResponse = executeRequest(uri);
int status = contentResponse.getStatus();
String response = contentResponse.getContentAsString();
logger.debug("getBridgeInfo status[{}] response[{}]", status, response);
if (status == HttpStatus.OK_200) {
BridgeApiInfoDto bridgeApiInfoDto = gson.fromJson(response, BridgeApiInfoDto.class);
logger.debug("getBridgeInfo OK");
return new BridgeInfoResponse(status, contentResponse.getReason(), bridgeApiInfoDto);
} else {
logger.debug("Could not get Bridge Info! Status[{}] - Response[{}]", status, response);
return new BridgeInfoResponse(status, contentResponse.getReason(), null);
}
} catch (Exception e) {
logger.debug("Could not get Bridge Info! Exception[{}]", e.getMessage());
return new BridgeInfoResponse(handleException(e));
}
}
public BridgeLockStateResponse getBridgeLockState(String nukiId) {
logger.debug("getBridgeLockState({}) in thread {}", nukiId, Thread.currentThread().getId());
long timestampSecs = Instant.now().getEpochSecond();
if (this.bridgeLockStateResponseCache != null
&& timestampSecs < this.bridgeLockStateResponseCache.getCreated().getEpochSecond() + CACHE_PERIOD) {
logger.debug("Returning LockState from cache - now[{}]<created[{}]+cachePeriod[{}]", timestampSecs,
this.bridgeLockStateResponseCache.getCreated().getEpochSecond(), CACHE_PERIOD);
return bridgeLockStateResponseCache;
} else {
logger.debug("Requesting LockState from Bridge.");
}
String uri = prepareUri(NukiBindingConstants.URI_LOCKSTATE, nukiId);
try {
ContentResponse contentResponse = executeRequest(uri);
int status = contentResponse.getStatus();
String response = contentResponse.getContentAsString();
logger.debug("getBridgeLockState status[{}] response[{}]", status, response);
if (status == HttpStatus.OK_200) {
BridgeApiLockStateDto bridgeApiLockStateDto = gson.fromJson(response, BridgeApiLockStateDto.class);
logger.debug("getBridgeLockState OK");
return bridgeLockStateResponseCache = new BridgeLockStateResponse(status, contentResponse.getReason(),
bridgeApiLockStateDto);
} else {
logger.debug("Could not get Lock State! Status[{}] - Response[{}]", status, response);
return new BridgeLockStateResponse(status, contentResponse.getReason(), null);
}
} catch (Exception e) {
logger.debug("Could not get Bridge Lock State! Exception[{}]", e.getMessage());
return new BridgeLockStateResponse(handleException(e));
}
}
public BridgeLockActionResponse getBridgeLockAction(String nukiId, int lockAction) {
logger.debug("getBridgeLockAction({}, {}) in thread {}", nukiId, lockAction, Thread.currentThread().getId());
String uri = prepareUri(NukiBindingConstants.URI_LOCKACTION, nukiId, Integer.toString(lockAction));
try {
ContentResponse contentResponse = executeRequest(uri);
int status = contentResponse.getStatus();
String response = contentResponse.getContentAsString();
logger.debug("getBridgeLockAction status[{}] response[{}]", status, response);
if (status == HttpStatus.OK_200) {
BridgeApiLockActionDto bridgeApiLockActionDto = gson.fromJson(response, BridgeApiLockActionDto.class);
logger.debug("getBridgeLockAction OK");
return new BridgeLockActionResponse(status, contentResponse.getReason(), bridgeApiLockActionDto);
} else {
logger.debug("Could not execute Lock Action! Status[{}] - Response[{}]", status, response);
return new BridgeLockActionResponse(status, contentResponse.getReason(), null);
}
} catch (Exception e) {
logger.debug("Could not execute Lock Action! Exception[{}]", e.getMessage());
return new BridgeLockActionResponse(handleException(e));
}
}
public BridgeCallbackAddResponse getBridgeCallbackAdd(String callbackUrl) {
logger.debug("getBridgeCallbackAdd({}) in thread {}", callbackUrl, Thread.currentThread().getId());
try {
String uri = prepareUri(NukiBindingConstants.URI_CBADD, URLEncoder.encode(callbackUrl, "UTF-8"));
ContentResponse contentResponse = executeRequest(uri);
int status = contentResponse.getStatus();
String response = contentResponse.getContentAsString();
logger.debug("getBridgeCallbackAdd status[{}] response[{}]", status, response);
if (status == HttpStatus.OK_200) {
BridgeApiCallbackAddDto bridgeApiCallbackAddDto = gson.fromJson(response,
BridgeApiCallbackAddDto.class);
logger.debug("getBridgeCallbackAdd OK");
return new BridgeCallbackAddResponse(status, contentResponse.getReason(), bridgeApiCallbackAddDto);
} else {
logger.debug("Could not execute Callback Add! Status[{}] - Response[{}]", status, response);
return new BridgeCallbackAddResponse(status, contentResponse.getReason(), null);
}
} catch (Exception e) {
logger.debug("Could not execute Callback Add! Exception[{}]", e.getMessage());
return new BridgeCallbackAddResponse(handleException(e));
}
}
public BridgeCallbackListResponse getBridgeCallbackList() {
logger.debug("getBridgeCallbackList() in thread {}", Thread.currentThread().getId());
String uri = prepareUri(NukiBindingConstants.URI_CBLIST);
try {
ContentResponse contentResponse = executeRequest(uri);
int status = contentResponse.getStatus();
String response = contentResponse.getContentAsString();
logger.debug("getBridgeCallbackList status[{}] response[{}]", status, response);
if (status == HttpStatus.OK_200) {
BridgeApiCallbackListDto bridgeApiCallbackListDto = gson.fromJson(response,
BridgeApiCallbackListDto.class);
logger.debug("getBridgeCallbackList OK");
return new BridgeCallbackListResponse(status, contentResponse.getReason(), bridgeApiCallbackListDto);
} else {
logger.debug("Could not execute Callback List! Status[{}] - Response[{}]", status, response);
return new BridgeCallbackListResponse(status, contentResponse.getReason(), null);
}
} catch (Exception e) {
logger.debug("Could not execute Callback List! Exception[{}]", e.getMessage());
return new BridgeCallbackListResponse(handleException(e));
}
}
public BridgeCallbackRemoveResponse getBridgeCallbackRemove(int id) {
logger.debug("getBridgeCallbackRemove({}) in thread {}", id, Thread.currentThread().getId());
try {
String uri = prepareUri(NukiBindingConstants.URI_CBREMOVE, Integer.toString(id));
ContentResponse contentResponse = executeRequest(uri);
int status = contentResponse.getStatus();
String response = contentResponse.getContentAsString();
logger.debug("getBridgeCallbackRemove status[{}] response[{}]", status, response);
if (status == HttpStatus.OK_200) {
BridgeApiCallbackRemoveDto bridgeApiCallbackRemoveDto = gson.fromJson(response,
BridgeApiCallbackRemoveDto.class);
logger.debug("getBridgeCallbackRemove OK");
return new BridgeCallbackRemoveResponse(status, contentResponse.getReason(),
bridgeApiCallbackRemoveDto);
} else {
logger.debug("Could not execute Callback Remove! Status[{}] - Response[{}]", status, response);
return new BridgeCallbackRemoveResponse(status, contentResponse.getReason(), null);
}
} catch (Exception e) {
logger.debug("Could not execute Callback Remove! Exception[{}]", e.getMessage());
return new BridgeCallbackRemoveResponse(handleException(e));
}
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiCallbackAddDto} class defines the Data Transfer Object (POJO)
* for the Nuki Bridge API /callback/add endpoint.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiCallbackAddDto {
private boolean success;
private String message;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiCallbackListCallbackDto} class defines the Data Transfer Object (POJO)
* for the Nuki Bridge API /callback/list endpoint.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiCallbackListCallbackDto {
private int id;
private String url;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
import java.util.List;
/**
* The {@link BridgeApiCallbackListDto} class defines the Data Transfer Object (POJO)
* for the Nuki Bridge API /callback/list endpoint.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiCallbackListDto {
private List<BridgeApiCallbackListCallbackDto> callbacks;
public List<BridgeApiCallbackListCallbackDto> getCallbacks() {
return callbacks;
}
public void setCallbacks(List<BridgeApiCallbackListCallbackDto> callbacks) {
this.callbacks = callbacks;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiCallbackRemoveDto} class defines the Data Transfer Object (POJO)
* for the Nuki Bridge API /callback/remove endpoint.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiCallbackRemoveDto {
private boolean success;
private String message;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
import java.util.List;
/**
* The {@link BridgeApiInfoDto} class defines the Data Transfer Object (POJO) for the Nuki Bridge API /info endpoint.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiInfoDto {
private int bridgeType;
private BridgeApiInfoIdDto ids;
private BridgeApiInfoVersionDto versions;
private int uptime;
private String currentTime;
private boolean serverConnected;
private List<BridgeApiInfoScanResultDto> scanResults;
public int getBridgeType() {
return bridgeType;
}
public void setBridgeType(int bridgeType) {
this.bridgeType = bridgeType;
}
public BridgeApiInfoIdDto getIds() {
return ids;
}
public void setIds(BridgeApiInfoIdDto ids) {
this.ids = ids;
}
public BridgeApiInfoVersionDto getVersions() {
return versions;
}
public void setVersions(BridgeApiInfoVersionDto versions) {
this.versions = versions;
}
public int getUptime() {
return uptime;
}
public void setUptime(int uptime) {
this.uptime = uptime;
}
public String getCurrentTime() {
return currentTime;
}
public void setCurrentTime(String currentTime) {
this.currentTime = currentTime;
}
public boolean isServerConnected() {
return serverConnected;
}
public void setServerConnected(boolean serverConnected) {
this.serverConnected = serverConnected;
}
public List<BridgeApiInfoScanResultDto> getScanResults() {
return scanResults;
}
public void setScanResults(List<BridgeApiInfoScanResultDto> scanResults) {
this.scanResults = scanResults;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiInfoIdDto} class defines the Data Transfer Object (POJO) for the Nuki Bridge API /info endpoint.
* It is a nested JSON object of {@link BridgeApiInfoDto}.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiInfoIdDto {
private int hardwareId;
private int serverId;
public int getHardwareId() {
return hardwareId;
}
public void setHardwareId(int hardwareId) {
this.hardwareId = hardwareId;
}
public int getServerId() {
return serverId;
}
public void setServerId(int serverId) {
this.serverId = serverId;
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiInfoScanResultDto} class defines the Data Transfer Object (POJO) for the Nuki Bridge API /info
* endpoint.
* It is a nested JSON object of {@link BridgeApiInfoDto}.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiInfoScanResultDto {
private int nukiId;
private String name;
private int rssi;
private boolean paired;
public int getNukiId() {
return nukiId;
}
public void setNukiId(int nukiId) {
this.nukiId = nukiId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getRssi() {
return rssi;
}
public void setRssi(int rssi) {
this.rssi = rssi;
}
public boolean isPaired() {
return paired;
}
public void setPaired(boolean paired) {
this.paired = paired;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiInfoVersionDto} class defines the Data Transfer Object (POJO) for the Nuki Bridge API /list
* endpoint.
* It is a nested JSON object of {@link BridgeApiInfoDto}.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiInfoVersionDto {
private String firmwareVersion;
private String wifiFirmwareVersion;
public String getFirmwareVersion() {
return firmwareVersion;
}
public void setFirmwareVersion(String firmwareVersion) {
this.firmwareVersion = firmwareVersion;
}
public String getWifiFirmwareVersion() {
return wifiFirmwareVersion;
}
public void setWifiFirmwareVersion(String wifiFirmwareVersion) {
this.wifiFirmwareVersion = wifiFirmwareVersion;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiLockActionDto} class defines the Data Transfer Object (POJO) for the Nuki Bridge API /lockAction
* endpoint.
*
* @author Markus Katter - Initial contribution
*/
public class BridgeApiLockActionDto {
private boolean success;
private boolean batteryCritical;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public boolean isBatteryCritical() {
return batteryCritical;
}
public void setBatteryCritical(boolean batteryCritical) {
this.batteryCritical = batteryCritical;
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiLockStateDto} class defines the Data Transfer Object (POJO) for the Nuki Bridge API /lockState
* endpoint.
*
* @author Markus Katter - Initial contribution
* @contributer Christian Hoefler - Door sensor integration
*/
public class BridgeApiLockStateDto {
private int state;
private String stateName;
private boolean batteryCritical;
private int doorsensorState;
private boolean success;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
public boolean isBatteryCritical() {
return batteryCritical;
}
public void setBatteryCritical(boolean batteryCritical) {
this.batteryCritical = batteryCritical;
}
public int getDoorsensorState() {
return doorsensorState;
}
public void setDoorsensorState(int doorsensorState) {
this.doorsensorState = doorsensorState;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link BridgeApiLockStateRequestDto} class defines the Data Transfer Object (POJO) which is send from the Nuki
* Bridge to the openHAB Server.
*
* @author Markus Katter - Initial contribution
* @contributer Christian Hoefler - Door sensor integration
*/
public class BridgeApiLockStateRequestDto {
private int nukiId;
private int state;
private String stateName;
private boolean batteryCritical;
private int doorsensorState;
public int getNukiId() {
return nukiId;
}
public void setNukiId(int nukiId) {
this.nukiId = nukiId;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getStateName() {
return stateName;
}
public void setStateName(String stateName) {
this.stateName = stateName;
}
public boolean isBatteryCritical() {
return batteryCritical;
}
public void setBatteryCritical(boolean batteryCritical) {
this.batteryCritical = batteryCritical;
}
public int getDoorsensorState() {
return doorsensorState;
}
public void setDoorsensorState(int doorsensorState) {
this.doorsensorState = doorsensorState;
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.dto;
/**
* The {@link NukiHttpServerStatusResponseDto} class defines the Data Transfer Object (POJO) for a status response of
* the Nuki HTTP
* Server.
*
* @author Markus Katter - Initial contribution
*/
public class NukiHttpServerStatusResponseDto {
private String status;
public NukiHttpServerStatusResponseDto(String status) {
super();
this.status = status;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@@ -0,0 +1,194 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.handler;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.nuki.internal.NukiBindingConstants;
import org.openhab.binding.nuki.internal.dataexchange.BridgeCallbackAddResponse;
import org.openhab.binding.nuki.internal.dataexchange.BridgeCallbackListResponse;
import org.openhab.binding.nuki.internal.dataexchange.BridgeCallbackRemoveResponse;
import org.openhab.binding.nuki.internal.dataexchange.BridgeInfoResponse;
import org.openhab.binding.nuki.internal.dataexchange.NukiHttpClient;
import org.openhab.binding.nuki.internal.dto.BridgeApiCallbackListCallbackDto;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NukiBridgeHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Markus Katter - Initial contribution
*/
public class NukiBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(NukiBridgeHandler.class);
private static final int JOB_INTERVAL = 600;
private HttpClient httpClient;
private NukiHttpClient nukiHttpClient;
private String callbackUrl;
private ScheduledFuture<?> checkBridgeOnlineJob;
private String bridgeIp;
private boolean manageCallbacks;
private boolean initializable;
public NukiBridgeHandler(Bridge bridge, HttpClient httpClient, String callbackUrl) {
super(bridge);
logger.debug("Instantiating NukiBridgeHandler({}, {}, {})", bridge, httpClient, callbackUrl);
this.httpClient = httpClient;
this.callbackUrl = callbackUrl;
this.initializable = getConfig().get(NukiBindingConstants.CONFIG_IP) != null
&& getConfig().get(NukiBindingConstants.CONFIG_API_TOKEN) != null;
}
public NukiHttpClient getNukiHttpClient() {
if (nukiHttpClient == null) {
nukiHttpClient = new NukiHttpClient(httpClient, getConfig());
}
return nukiHttpClient;
}
public boolean isInitializable() {
return initializable;
}
@Override
public void initialize() {
logger.debug("initialize() for Bridge[{}].", getThing().getUID());
Configuration config = getConfig();
bridgeIp = (String) config.get(NukiBindingConstants.CONFIG_IP);
manageCallbacks = (Boolean) config.get(NukiBindingConstants.CONFIG_MANAGECB);
if (bridgeIp == null) {
logger.debug("NukiBridgeHandler[{}] is not initializable, IP setting is unset in the configuration!",
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP setting is unset");
} else if (config.get(NukiBindingConstants.CONFIG_API_TOKEN) == null) {
logger.debug("NukiBridgeHandler[{}] is not initializable, apiToken setting is unset in the configuration!",
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "apiToken setting is unset");
} else {
scheduler.execute(this::initializeHandler);
checkBridgeOnlineJob = scheduler.scheduleWithFixedDelay(this::checkBridgeOnline, JOB_INTERVAL, JOB_INTERVAL,
TimeUnit.SECONDS);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand({}, {}) for Bridge[{}] not implemented!", channelUID, command, bridgeIp);
}
@Override
public void dispose() {
logger.debug("dispose() for Bridge[{}].", getThing().getUID());
nukiHttpClient = null;
if (checkBridgeOnlineJob != null && !checkBridgeOnlineJob.isCancelled()) {
checkBridgeOnlineJob.cancel(true);
}
checkBridgeOnlineJob = null;
}
private synchronized void initializeHandler() {
logger.debug("initializeHandler() for Bridge[{}].", bridgeIp);
BridgeInfoResponse bridgeInfoResponse = getNukiHttpClient().getBridgeInfo();
if (bridgeInfoResponse.getStatus() == HttpStatus.OK_200) {
if (manageCallbacks) {
manageNukiBridgeCallbacks();
}
logger.debug("Bridge[{}] responded with status[{}]. Switching the bridge online.", bridgeIp,
bridgeInfoResponse.getStatus());
updateStatus(ThingStatus.ONLINE);
} else {
logger.debug("Bridge[{}] responded with status[{}]. Switching the bridge offline!", bridgeIp,
bridgeInfoResponse.getStatus());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, bridgeInfoResponse.getMessage());
}
}
private void checkBridgeOnline() {
logger.debug("checkBridgeOnline():bridgeIp[{}] status[{}]", bridgeIp, getThing().getStatus());
if (getThing().getStatus().equals(ThingStatus.ONLINE)) {
logger.debug("Requesting BridgeInfo to ensure Bridge[{}] is online.", bridgeIp);
BridgeInfoResponse bridgeInfoResponse = getNukiHttpClient().getBridgeInfo();
int status = bridgeInfoResponse.getStatus();
if (status == HttpStatus.OK_200) {
logger.debug("Bridge[{}] responded with status[{}]. Bridge is online.", bridgeIp, status);
} else if (status == HttpStatus.SERVICE_UNAVAILABLE_503) {
logger.debug(
"Bridge[{}] responded with status[{}]. REST service seems to be busy but Bridge is online.",
bridgeIp, status);
} else {
logger.debug("Bridge[{}] responded with status[{}]. Switching the bridge offline!", bridgeIp, status);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
bridgeInfoResponse.getMessage());
}
} else {
initializeHandler();
}
}
private void manageNukiBridgeCallbacks() {
logger.debug("manageNukiBridgeCallbacks() for Bridge[{}].", bridgeIp);
BridgeCallbackListResponse bridgeCallbackListResponse = getNukiHttpClient().getBridgeCallbackList();
List<BridgeApiCallbackListCallbackDto> callbacks = bridgeCallbackListResponse.getCallbacks();
boolean callbackExists = false;
int callbackCount = callbacks == null ? 0 : callbacks.size();
if (callbacks != null) {
for (BridgeApiCallbackListCallbackDto callback : callbacks) {
if (callback.getUrl().equals(callbackUrl)) {
logger.debug("callbackUrl[{}] already existing on Bridge[{}].", callbackUrl, bridgeIp);
callbackExists = true;
continue;
}
if (callback.getUrl().contains(NukiBindingConstants.CALLBACK_ENDPOINT)) {
logger.debug("Partial callbackUrl[{}] found on Bridge[{}] - Removing it!", callbackUrl, bridgeIp);
BridgeCallbackRemoveResponse bridgeCallbackRemoveResponse = getNukiHttpClient()
.getBridgeCallbackRemove(callback.getId());
if (bridgeCallbackRemoveResponse.getStatus() == HttpStatus.OK_200) {
logger.debug("Successfully removed callbackUrl[{}] on Bridge[{}]!", callbackUrl, bridgeIp);
callbackCount--;
}
}
}
}
if (!callbackExists) {
if (callbackCount == 3) {
logger.debug("Already 3 callback URLs existing on Bridge[{}] - Removing ID 0!", bridgeIp);
BridgeCallbackRemoveResponse bridgeCallbackRemoveResponse = getNukiHttpClient()
.getBridgeCallbackRemove(0);
if (bridgeCallbackRemoveResponse.getStatus() == HttpStatus.OK_200) {
logger.debug("Successfully removed callbackUrl[{}] on Bridge[{}]!", callbackUrl, bridgeIp);
callbackCount--;
}
}
logger.debug("Adding callbackUrl[{}] to Bridge[{}]!", callbackUrl, bridgeIp);
BridgeCallbackAddResponse bridgeCallbackAddResponse = getNukiHttpClient().getBridgeCallbackAdd(callbackUrl);
if (bridgeCallbackAddResponse.getStatus() == HttpStatus.OK_200) {
logger.debug("Successfully added callbackUrl[{}] on Bridge[{}]!", callbackUrl, bridgeIp);
callbackExists = true;
}
}
}
}

View File

@@ -0,0 +1,289 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.nuki.internal.handler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.nuki.internal.NukiBindingConstants;
import org.openhab.binding.nuki.internal.converter.LockActionConverter;
import org.openhab.binding.nuki.internal.dataexchange.BridgeLockActionResponse;
import org.openhab.binding.nuki.internal.dataexchange.BridgeLockStateResponse;
import org.openhab.binding.nuki.internal.dataexchange.NukiBaseResponse;
import org.openhab.binding.nuki.internal.dataexchange.NukiHttpClient;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NukiSmartLockHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Markus Katter - Initial contribution
* @contributer Christian Hoefler - Door sensor integration
*/
public class NukiSmartLockHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(NukiSmartLockHandler.class);
private static final int JOB_INTERVAL = 60;
private NukiHttpClient nukiHttpClient;
private ScheduledFuture<?> reInitJob;
private String nukiId;
private boolean unlatch;
public NukiSmartLockHandler(Thing thing) {
super(thing);
logger.debug("Instantiating NukiSmartLockHandler({})", thing);
}
@Override
public void initialize() {
logger.debug("initialize() for Smart Lock[{}].", getThing().getUID());
Configuration config = getConfig();
nukiId = (String) config.get(NukiBindingConstants.CONFIG_NUKI_ID);
unlatch = (Boolean) config.get(NukiBindingConstants.CONFIG_UNLATCH);
if (nukiId == null) {
logger.debug("NukiSmartLockHandler[{}] is not initializable, nukiId setting is unset in the configuration!",
getThing().getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "nukiId setting is unset");
} else {
scheduler.execute(this::initializeHandler);
}
}
@Override
public void dispose() {
logger.debug("dispose() for Smart Lock[{}].", getThing().getUID());
stopReInitJob();
}
private void initializeHandler() {
logger.debug("initializeHandler() for Smart Lock[{}]", nukiId);
Bridge bridge = getBridge();
if (bridge == null) {
initializeHandler(null, null);
} else {
initializeHandler(bridge.getHandler(), bridge.getStatus());
}
}
private void initializeHandler(ThingHandler bridgeHandler, ThingStatus bridgeStatus) {
if (bridgeHandler != null && bridgeStatus != null) {
if (bridgeStatus == ThingStatus.ONLINE) {
nukiHttpClient = ((NukiBridgeHandler) bridgeHandler).getNukiHttpClient();
BridgeLockStateResponse bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
if (handleResponse(bridgeLockStateResponse, null, null)) {
updateStatus(ThingStatus.ONLINE);
for (Channel channel : thing.getChannels()) {
handleCommand(channel.getUID(), RefreshType.REFRESH);
}
stopReInitJob();
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
stopReInitJob();
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
stopReInitJob();
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
logger.debug("bridgeStatusChanged({}) for Smart Lock[{}].", bridgeStatusInfo, nukiId);
scheduler.execute(() -> {
Bridge bridge = getBridge();
if (bridge == null) {
initializeHandler(null, bridgeStatusInfo.getStatus());
} else {
initializeHandler(bridge.getHandler(), bridgeStatusInfo.getStatus());
}
});
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("handleCommand({}, {})", channelUID, command);
if (getThing().getStatus() != ThingStatus.ONLINE) {
logger.debug("Thing is not ONLINE; command[{}] for channelUID[{}] is ignored", command, channelUID);
return;
}
if (command instanceof RefreshType) {
handleCommandRefreshType(channelUID, command);
return;
}
boolean validCmd = true;
switch (channelUID.getId()) {
case NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK:
if (command instanceof OnOffType) {
int lockAction;
if (unlatch) {
lockAction = (command == OnOffType.OFF ? NukiBindingConstants.LOCK_ACTIONS_UNLATCH
: NukiBindingConstants.LOCK_ACTIONS_LOCK);
} else {
lockAction = (command == OnOffType.OFF ? NukiBindingConstants.LOCK_ACTIONS_UNLOCK
: NukiBindingConstants.LOCK_ACTIONS_LOCK);
}
Channel channelLockState = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_STATE);
if (channelLockState != null) {
updateState(channelLockState.getUID(),
new DecimalType(LockActionConverter.getLockStateFor(lockAction)));
}
BridgeLockActionResponse bridgeLockActionResponse = nukiHttpClient.getBridgeLockAction(nukiId,
lockAction);
handleResponse(bridgeLockActionResponse, channelUID.getAsString(), command.toString());
} else {
validCmd = false;
}
break;
case NukiBindingConstants.CHANNEL_SMARTLOCK_STATE:
if (command instanceof DecimalType) {
int lockAction;
lockAction = ((DecimalType) command).intValue();
lockAction = LockActionConverter.getLockActionFor(lockAction);
updateState(channelUID, new DecimalType(LockActionConverter.getLockStateFor(lockAction)));
BridgeLockActionResponse bridgeLockActionResponse = nukiHttpClient.getBridgeLockAction(nukiId,
lockAction);
handleResponse(bridgeLockActionResponse, channelUID.getAsString(), command.toString());
} else {
validCmd = false;
}
break;
default:
validCmd = false;
break;
}
if (!validCmd) {
logger.debug("Unexpected command[{}] for channelUID[{}]!", command, channelUID);
}
}
private void handleCommandRefreshType(ChannelUID channelUID, Command command) {
logger.debug("handleCommandRefreshType({}, {})", channelUID, command);
BridgeLockStateResponse bridgeLockStateResponse;
switch (channelUID.getId()) {
case NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK:
bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
if (handleResponse(bridgeLockStateResponse, channelUID.getAsString(), command.toString())) {
int lockState = bridgeLockStateResponse.getState();
State state;
if (lockState == NukiBindingConstants.LOCK_STATES_LOCKED) {
state = OnOffType.ON;
} else if (lockState == NukiBindingConstants.LOCK_STATES_UNLOCKED) {
state = OnOffType.OFF;
} else {
logger.warn(
"Smart Lock returned lockState[{}]. Intentionally setting possibly wrong value 'OFF' for channel 'smartlockLock'!",
lockState);
state = OnOffType.OFF;
}
updateState(channelUID, state);
}
break;
case NukiBindingConstants.CHANNEL_SMARTLOCK_STATE:
bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
if (handleResponse(bridgeLockStateResponse, channelUID.getAsString(), command.toString())) {
updateState(channelUID, new DecimalType(bridgeLockStateResponse.getState()));
}
break;
case NukiBindingConstants.CHANNEL_SMARTLOCK_LOW_BATTERY:
bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
if (handleResponse(bridgeLockStateResponse, channelUID.getAsString(), command.toString())) {
updateState(channelUID, bridgeLockStateResponse.isBatteryCritical() ? OnOffType.ON : OnOffType.OFF);
}
break;
case NukiBindingConstants.CHANNEL_SMARTLOCK_DOOR_STATE:
bridgeLockStateResponse = nukiHttpClient.getBridgeLockState(nukiId);
if (handleResponse(bridgeLockStateResponse, channelUID.getAsString(), command.toString())) {
updateState(channelUID, new DecimalType(bridgeLockStateResponse.getDoorsensorState()));
}
break;
default:
logger.debug("Command[{}] for channelUID[{}] not implemented!", command, channelUID);
return;
}
}
private boolean handleResponse(NukiBaseResponse nukiBaseResponse, String channelUID, String command) {
if (nukiBaseResponse.getStatus() == 200 && nukiBaseResponse.isSuccess()) {
logger.debug("Command[{}] succeeded for channelUID[{}] on nukiId[{}]!", command, channelUID, nukiId);
return true;
} else if (nukiBaseResponse.getStatus() != 200) {
logger.debug("Request to Bridge failed! status[{}] - message[{}]", nukiBaseResponse.getStatus(),
nukiBaseResponse.getMessage());
} else if (!nukiBaseResponse.isSuccess()) {
logger.debug(
"Request from Bridge to Smart Lock failed! status[{}] - message[{}] - isSuccess[{}]. Check if Nuki Smart Lock is powered on!",
nukiBaseResponse.getStatus(), nukiBaseResponse.getMessage(), nukiBaseResponse.isSuccess());
}
logger.debug("Could not handle command[{}] for channelUID[{}] on nukiId[{}]!", command, channelUID, nukiId);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, nukiBaseResponse.getMessage());
Channel channelLock = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_LOCK);
if (channelLock != null) {
updateState(channelLock.getUID(), OnOffType.OFF);
}
Channel channelLockState = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_STATE);
if (channelLockState != null) {
updateState(channelLockState.getUID(), new DecimalType(NukiBindingConstants.LOCK_STATES_UNDEFINED));
}
Channel channelDoorState = thing.getChannel(NukiBindingConstants.CHANNEL_SMARTLOCK_DOOR_STATE);
if (channelDoorState != null) {
updateState(channelDoorState.getUID(), new DecimalType(NukiBindingConstants.DOORSENSOR_STATES_UNKNOWN));
}
startReInitJob();
return false;
}
private void startReInitJob() {
logger.trace("Starting reInitJob with interval of {}secs for Smart Lock[{}].", JOB_INTERVAL, nukiId);
if (reInitJob != null) {
logger.trace("Already started reInitJob for Smart Lock[{}].", nukiId);
return;
}
reInitJob = scheduler.scheduleWithFixedDelay(this::initializeHandler, JOB_INTERVAL, JOB_INTERVAL,
TimeUnit.SECONDS);
}
private void stopReInitJob() {
logger.trace("Stopping reInitJob for Smart Lock[{}].", nukiId);
if (reInitJob != null && !reInitJob.isCancelled()) {
logger.trace("Stopped reInitJob for Smart Lock[{}].", nukiId);
reInitJob.cancel(true);
}
reInitJob = null;
}
public void handleApiServletUpdate(ChannelUID channelUID, State newState) {
logger.trace("handleApiServletUpdate({}, {})", channelUID, newState);
updateState(channelUID, newState);
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="nuki" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Nuki Binding</name>
<description>The Nuki Binding allows simple and fast integration of Nuki Smart Locks into openHAB. This binding needs
the Nuki Smart Lock(s) to be paired via Bluetooth with a Nuki Bridge to function correctly.</description>
<author>Markus Katter</author>
</binding:binding>

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="nuki"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Nuki Bridge (Bridge Type) -->
<bridge-type id="bridge">
<label>Nuki Bridge</label>
<description>This bridge represents a Nuki Bridge on your local network. Nuki Smart Locks have to be paired via
Bluetooth with it.</description>
<config-description>
<parameter name="ip" type="text" required="true">
<label>IP Address</label>
<context>network-address</context>
<description>The IP address of the Nuki Bridge. Look it up on your router. It is recommended to set a static IP
address lease for the Nuki Bridge (and for your openHAB server too) on your router.</description>
</parameter>
<parameter name="port" type="integer" required="false">
<label>Port</label>
<description>The Port which you configured during Initial Bridge setup
(https://nuki.io/en/support/bridge/bridge-setup/initial-bridge-setup/).</description>
<default>8080</default>
</parameter>
<parameter name="apiToken" type="text" required="true">
<label>API Token</label>
<context>password</context>
<description>The API Token which you configured during Initial Bridge setup
(https://nuki.io/en/support/bridge/bridge-setup/initial-bridge-setup/).</description>
</parameter>
<parameter name="manageCallbacks" type="boolean" required="false">
<label>Manage Nuki Bridge Callbacks</label>
<description>Let the Nuki Binding manage the callback on the Nuki Bridge.</description>
<default>true</default>
</parameter>
</config-description>
</bridge-type>
<!-- Nuki Smart Lock (Thing Type) -->
<thing-type id="smartlock">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Nuki Smart Lock</label>
<description>Nuki Smart Lock which is paired via Bluetooth to a Nuki Bridge.</description>
<channels>
<channel id="lock" typeId="smartlockLock"/>
<channel id="lockState" typeId="smartlockState"/>
<channel id="lowBattery" typeId="system.low-battery"/>
<channel id="doorsensorState" typeId="smartlockDoorState"/>
</channels>
<config-description>
<parameter name="nukiId" type="text" required="true">
<label>Nuki ID</label>
<description>The 8-digit hexadecimal string that identifies the Nuki Smart Lock. Look it up on the sticker on the
back of the Nuki Smart Lock (remove mounting plate).</description>
</parameter>
<parameter name="unlatch" type="boolean" required="false">
<label>Unlatch</label>
<description>If switched to On (or set to true) the Nuki Smart Lock will unlock the door but then also automatically
pull the latch of the door lock. Usually, if the door hinges are correctly adjusted, the door will then swing open.</description>
<default>false</default>
</parameter>
</config-description>
</thing-type>
<!-- Channel Type -->
<channel-type id="smartlockLock">
<item-type>Switch</item-type>
<label>Lock</label>
<description>Use this channel with a Switch Item to unlock and lock the door. Configure "Unlatch" to true if your Nuki
Smart Lock is mounted on a door lock with a knob on the outside.</description>
<category>Door</category>
<state>
<options>
<option value="OFF">Unlocks the door</option>
<option value="ON">Locks the door</option>
</options>
</state>
</channel-type>
<channel-type id="smartlockState">
<item-type>Number</item-type>
<label>Lock State</label>
<description>Use this channel if you want to execute other supported lock actions or to display the current lock
state.</description>
<category>Door</category>
<state>
<options>
<option value="0">Uncalibrated</option>
<option value="1">Locked</option>
<option value="2">Unlocking</option>
<option value="3">Unlocked</option>
<option value="4">Locking</option>
<option value="5">Unlatched</option>
<option value="6">Unlocked (Lock 'n' Go)</option>
<option value="7">Unlatching</option>
<option value="1002">Unlocking (Lock 'n' Go)</option>
<option value="1007">Unlatching (Lock 'n' Go)</option>
<option value="254">Motor blocked</option>
<option value="255">UNDEFINED</option>
</options>
</state>
</channel-type>
<channel-type id="smartlockDoorState">
<item-type>Number</item-type>
<label>Door State</label>
<description>Use this channel to display the current state of the door sensor</description>
<category>Door</category>
<state>
<options>
<option value="0">Unavailable</option>
<option value="1">Deactivated</option>
<option value="2">Closed</option>
<option value="3">Open</option>
<option value="4">Unknown</option>
<option value="5">Calibrating</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions>