[nuki] Support for SmartLock 3.0 and SmartDoor (#12005)

Signed-off-by: Jan Vybíral <jan.vybiral1@gmail.com>
This commit is contained in:
Jan Vybíral 2022-01-10 09:36:28 +01:00 committed by GitHub
parent bcb7b73191
commit dc0a974a98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 34 deletions

View File

@ -57,7 +57,7 @@ connected to is configured and online.
### Nuki Smart Lock ### Nuki Smart Lock
The following configuration options are available: This is a common thing for all Nuki smart lock products - Nuki Smart Lock 1.0/2.0/3.0 (Pro) and Nuki Smart Door. The following configuration options are available:
| Parameter | Description | Comment | | Parameter | Description | Comment |
|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| |-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
@ -106,14 +106,16 @@ Unfortunately the Nuki Bridge is not reporting any transition states (e.g. for L
##### Supported doorSensorState values ##### Supported doorSensorState values
| State | Name | | State | Name |
|--------|--------------------------| |-------|---------------------|
| 0 | Unavailable | | 1 | Deactivated |
| 1 | Deactivated | | 2 | Closed |
| 2 | Closed | | 3 | Open |
| 3 | Open | | 4 | Door state unknonwn |
| 4 | Unknown | | 5 | Calibrating |
| 5 | Calibrating | | 16 | Uncalibrated |
| 240 | Removed |
| 255 | Unknown |
### Nuki Opener ### Nuki Opener
@ -175,7 +177,7 @@ A manual setup through files could look like this:
``` ```
Bridge nuki:bridge:NB1 [ ip="192.168.0.50", port=8080, apiToken="myS3cr3t!", manageCallbacks=true ] { Bridge nuki:bridge:NB1 [ ip="192.168.0.50", port=8080, apiToken="myS3cr3t!", manageCallbacks=true ] {
Thing smartlock SL1 [ nukiId="12AB89EF", unlatch=false ] Thing smartlock SL1 [ nukiId="12AB89EF", deviceType=0, unlatch=false ]
} }
``` ```

View File

@ -22,4 +22,5 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
@NonNullByDefault @NonNullByDefault
public class NukiDeviceConfiguration { public class NukiDeviceConfiguration {
public String nukiId = ""; public String nukiId = "";
public int deviceType;
} }

View File

@ -49,7 +49,8 @@ public class NukiBindingConstants {
// Device Types // Device Types
public static final int DEVICE_SMART_LOCK = 0; public static final int DEVICE_SMART_LOCK = 0;
public static final int DEVICE_OPENER = 2; public static final int DEVICE_OPENER = 2;
public static final Set<Integer> SUPPORTED_DEVICES = Set.of(DEVICE_OPENER, DEVICE_SMART_LOCK); public static final int DEVICE_SMART_DOOR = 3;
public static final int DEVICE_SMART_LOCK_3 = 4;
// Properties // Properties
public static final String PROPERTY_WIFI_FIRMWARE_VERSION = "wifiFirmwareVersion"; public static final String PROPERTY_WIFI_FIRMWARE_VERSION = "wifiFirmwareVersion";
@ -59,6 +60,7 @@ public class NukiBindingConstants {
public static final String PROPERTY_NAME = "name"; public static final String PROPERTY_NAME = "name";
public static final String PROPERTY_NUKI_ID = "nukiId"; public static final String PROPERTY_NUKI_ID = "nukiId";
public static final String PROPERTY_BRIDGE_ID = "bridgeId"; public static final String PROPERTY_BRIDGE_ID = "bridgeId";
public static final String PROPERTY_DEVICE_TYPE = "deviceType";
// List of all Smart Lock Channel ids // List of all Smart Lock Channel ids
public static final String CHANNEL_SMARTLOCK_LOCK = "lock"; public static final String CHANNEL_SMARTLOCK_LOCK = "lock";

View File

@ -163,8 +163,8 @@ public class NukiHttpClient {
} }
} }
public BridgeLockActionResponse getSmartLockAction(String nukiId, SmartLockAction action) { public BridgeLockActionResponse getSmartLockAction(String nukiId, SmartLockAction action, int deviceType) {
return getBridgeLockAction(nukiId, action.getAction(), NukiBindingConstants.DEVICE_SMART_LOCK); return getBridgeLockAction(nukiId, action.getAction(), deviceType);
} }
public BridgeLockActionResponse getOpenerAction(String nukiId, OpenerAction action) { public BridgeLockActionResponse getOpenerAction(String nukiId, OpenerAction action) {

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.nuki.internal.discovery; package org.openhab.binding.nuki.internal.discovery;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -56,27 +57,41 @@ public class NukiDeviceDiscoveryService extends AbstractDiscoveryService impleme
scheduler.execute(() -> { scheduler.execute(() -> {
bridgeHandler.withHttpClient(client -> { bridgeHandler.withHttpClient(client -> {
BridgeListResponse list = client.getList(); BridgeListResponse list = client.getList();
list.getDevices().stream() list.getDevices().stream().map(device -> createDiscoveryResult(device, bridgeHandler))
.filter(device -> NukiBindingConstants.SUPPORTED_DEVICES.contains(device.getDeviceType())) .flatMap(Optional::stream).forEach(this::thingDiscovered);
.map(device -> createDiscoveryResult(device, bridgeHandler)).forEach(this::thingDiscovered);
}); });
}); });
} }
private DiscoveryResult createDiscoveryResult(BridgeApiListDeviceDto device, NukiBridgeHandler bridgeHandler) { private Optional<DiscoveryResult> createDiscoveryResult(BridgeApiListDeviceDto device,
return DiscoveryResultBuilder.create(getUid(device.getNukiId(), device.getDeviceType(), bridgeHandler)) NukiBridgeHandler bridgeHandler) {
.withBridge(bridgeHandler.getThing().getUID()).withLabel(device.getName()) ThingUID uid = getUid(device.getNukiId(), device.getDeviceType(), bridgeHandler);
.withRepresentationProperty(NukiBindingConstants.PROPERTY_NUKI_ID) if (uid == null) {
.withProperty(NukiBindingConstants.PROPERTY_NAME, device.getName()) logger.warn("Failed to create UID for device '{}' - deviceType '{}' is not supported", device,
.withProperty(NukiBindingConstants.PROPERTY_NUKI_ID, device.getNukiId()) device.getDeviceType());
.withProperty(NukiBindingConstants.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion()).build(); return Optional.empty();
} else {
return Optional.of(DiscoveryResultBuilder.create(uid).withBridge(bridgeHandler.getThing().getUID())
.withLabel(device.getName()).withRepresentationProperty(NukiBindingConstants.PROPERTY_NUKI_ID)
.withProperty(NukiBindingConstants.PROPERTY_NAME, device.getName())
.withProperty(NukiBindingConstants.PROPERTY_NUKI_ID, device.getNukiId())
.withProperty(NukiBindingConstants.PROPERTY_DEVICE_TYPE, device.getDeviceType())
.withProperty(NukiBindingConstants.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion()).build());
}
} }
@Nullable
private ThingUID getUid(String nukiId, int deviceType, NukiBridgeHandler bridgeHandler) { private ThingUID getUid(String nukiId, int deviceType, NukiBridgeHandler bridgeHandler) {
if (deviceType == NukiBindingConstants.DEVICE_OPENER) { switch (deviceType) {
return new ThingUID(NukiBindingConstants.THING_TYPE_OPENER, bridgeHandler.getThing().getUID(), nukiId); case NukiBindingConstants.DEVICE_OPENER:
} else { return new ThingUID(NukiBindingConstants.THING_TYPE_OPENER, bridgeHandler.getThing().getUID(), nukiId);
return new ThingUID(NukiBindingConstants.THING_TYPE_SMARTLOCK, bridgeHandler.getThing().getUID(), nukiId); case NukiBindingConstants.DEVICE_SMART_LOCK:
case NukiBindingConstants.DEVICE_SMART_DOOR:
case NukiBindingConstants.DEVICE_SMART_LOCK_3:
return new ThingUID(NukiBindingConstants.THING_TYPE_SMARTLOCK, bridgeHandler.getThing().getUID(),
nukiId);
default:
return null;
} }
} }

View File

@ -61,7 +61,7 @@ public class NukiSmartLockHandler extends AbstractNukiDeviceHandler<NukiSmartLoc
@Override @Override
protected int getDeviceType() { protected int getDeviceType() {
return NukiBindingConstants.DEVICE_SMART_LOCK; return this.configuration.deviceType;
} }
@Override @Override
@ -79,7 +79,7 @@ public class NukiSmartLockHandler extends AbstractNukiDeviceHandler<NukiSmartLoc
withHttpClient(client -> { withHttpClient(client -> {
BridgeLockActionResponse bridgeLockActionResponse = client BridgeLockActionResponse bridgeLockActionResponse = client
.getSmartLockAction(configuration.nukiId, action); .getSmartLockAction(configuration.nukiId, action, getDeviceType());
handleResponse(bridgeLockActionResponse, channelUID.getAsString(), command.toString()); handleResponse(bridgeLockActionResponse, channelUID.getAsString(), command.toString());
}); });
@ -93,7 +93,7 @@ public class NukiSmartLockHandler extends AbstractNukiDeviceHandler<NukiSmartLoc
if (action != null) { if (action != null) {
withHttpClient(client -> { withHttpClient(client -> {
BridgeLockActionResponse bridgeLockActionResponse = client BridgeLockActionResponse bridgeLockActionResponse = client
.getSmartLockAction(configuration.nukiId, action); .getSmartLockAction(configuration.nukiId, action, getDeviceType());
handleResponse(bridgeLockActionResponse, channelUID.getAsString(), command.toString()); handleResponse(bridgeLockActionResponse, channelUID.getAsString(), command.toString());
}); });
} }

View File

@ -26,6 +26,8 @@ thing-type.config.nuki.bridge.secureToken.label = Secure Token
thing-type.config.nuki.bridge.secureToken.description = Use hashed token when communicating with bridge. This increases security and prevents sniffing of access token and replay attacks, since communication with bridge is not encrypted. For this feature to work, both device running openHAB and Nuki Bridge must have synchronized time. When disabled, token is sent in plain text with each bridge request. It is recommended that this is turned on unless there are problems with synchronizing time between openHAB and Nuki Bridge. thing-type.config.nuki.bridge.secureToken.description = Use hashed token when communicating with bridge. This increases security and prevents sniffing of access token and replay attacks, since communication with bridge is not encrypted. For this feature to work, both device running openHAB and Nuki Bridge must have synchronized time. When disabled, token is sent in plain text with each bridge request. It is recommended that this is turned on unless there are problems with synchronizing time between openHAB and Nuki Bridge.
thing-type.config.nuki.opener.nukiId.label = Nuki ID thing-type.config.nuki.opener.nukiId.label = Nuki ID
thing-type.config.nuki.opener.nukiId.description = The decimal string that identifies the Nuki Opener. thing-type.config.nuki.opener.nukiId.description = The decimal string that identifies the Nuki Opener.
thing-type.config.nuki.smartlock.deviceType.label = Device Type
thing-type.config.nuki.smartlock.deviceType.description = Numeric device type as specified by bridge HTTP API - 0 = Nuki Smart Lock 1.0/2.0, 3 = Nuki Smart Door, 4 = Nuki Smart Lock 3.0 (Pro). Sent with each API request. Its purpose is not documented, seems to only be used for distinguishing between opener and smartlock actions. There does not seem to be any (documented or observable) differences between different smart lock device types.
thing-type.config.nuki.smartlock.nukiId.label = Nuki ID thing-type.config.nuki.smartlock.nukiId.label = Nuki ID
thing-type.config.nuki.smartlock.nukiId.description = The decimal string that identifies the Nuki Smart Lock. thing-type.config.nuki.smartlock.nukiId.description = The decimal string that identifies the Nuki Smart Lock.
thing-type.config.nuki.smartlock.unlatch.label = Unlatch thing-type.config.nuki.smartlock.unlatch.label = Unlatch
@ -61,12 +63,14 @@ channel-type.nuki.smartLockBatteryCharging.state.option.OFF = Battery is not cha
channel-type.nuki.smartLockBatteryCharging.state.option.ON = Battery is charging channel-type.nuki.smartLockBatteryCharging.state.option.ON = Battery is charging
channel-type.nuki.smartlockDoorState.label = Door State channel-type.nuki.smartlockDoorState.label = Door State
channel-type.nuki.smartlockDoorState.description = Use this channel to display the current state of the door sensor channel-type.nuki.smartlockDoorState.description = Use this channel to display the current state of the door sensor
channel-type.nuki.smartlockDoorState.state.option.0 = Unavailable
channel-type.nuki.smartlockDoorState.state.option.1 = Deactivated channel-type.nuki.smartlockDoorState.state.option.1 = Deactivated
channel-type.nuki.smartlockDoorState.state.option.2 = Closed channel-type.nuki.smartlockDoorState.state.option.2 = Closed
channel-type.nuki.smartlockDoorState.state.option.3 = Open channel-type.nuki.smartlockDoorState.state.option.3 = Open
channel-type.nuki.smartlockDoorState.state.option.4 = Unknown channel-type.nuki.smartlockDoorState.state.option.4 = Door state unknown
channel-type.nuki.smartlockDoorState.state.option.5 = Calibrating channel-type.nuki.smartlockDoorState.state.option.5 = Calibrating
channel-type.nuki.smartlockDoorState.state.option.16 = Uncalibrated
channel-type.nuki.smartlockDoorState.state.option.240 = Removed
channel-type.nuki.smartlockDoorState.state.option.255 = Unknown
channel-type.nuki.smartlockLock.label = Lock channel-type.nuki.smartlockLock.label = Lock
channel-type.nuki.smartlockLock.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. channel-type.nuki.smartlockLock.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.
channel-type.nuki.smartlockLock.state.option.OFF = Unlocks the door channel-type.nuki.smartlockLock.state.option.OFF = Unlocks the door

View File

@ -92,6 +92,23 @@
<label>Nuki ID</label> <label>Nuki ID</label>
<description>The decimal string that identifies the Nuki Smart Lock.</description> <description>The decimal string that identifies the Nuki Smart Lock.</description>
</parameter> </parameter>
<parameter name="deviceType" type="integer" required="true" readOnly="true">
<label>Device Type</label>
<default>0</default>
<options>
<option value="0">Nuki Smart Lock 1.0/2.0</option>
<option value="3">Nuki Smart Door</option>
<option value="4">Nuki Smart Lock 3.0 (Pro)</option>
</options>
<description>
Numeric device type as specified by bridge HTTP API - 0 = Nuki Smart Lock 1.0/2.0, 3 = Nuki Smart Door,
4 = Nuki Smart Lock 3.0 (Pro).
Sent with each API request. Its purpose is not documented, seems to only be used for
distinguishing between opener and smartlock actions.
There does not seem to be any (documented or observable)
differences between different smart lock device types.
</description>
</parameter>
</config-description> </config-description>
</thing-type> </thing-type>
@ -191,12 +208,14 @@
<category>Door</category> <category>Door</category>
<state readOnly="true"> <state readOnly="true">
<options> <options>
<option value="0">Unavailable</option>
<option value="1">Deactivated</option> <option value="1">Deactivated</option>
<option value="2">Closed</option> <option value="2">Closed</option>
<option value="3">Open</option> <option value="3">Open</option>
<option value="4">Unknown</option> <option value="4">Door state unknown</option>
<option value="5">Calibrating</option> <option value="5">Calibrating</option>
<option value="16">Uncalibrated</option>
<option value="240">Removed</option>
<option value="255">Unknown</option>
</options> </options>
</state> </state>
<autoUpdatePolicy>veto</autoUpdatePolicy> <autoUpdatePolicy>veto</autoUpdatePolicy>