Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bundles/org.openhab.binding.matter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ Possible channels include:
| colorcontrol-color | Color | Color | The color channel allows to control the color of a light. It is also possible to dim values and switch the light on and off. | ColorLight | | |
| colorcontrol-temperature | Dimmer | Color Temperature | Sets the color temperature of the light | ColorLight | | |
| colorcontrol-temperature-abs | Number:Temperature | Color Temperature | Sets the color temperature of the light in mirek | ColorLight | | %.0f %unit% |
| doorlock-lockstate | Switch | Door Lock State | Locks and unlocks the door and maintains the lock state | Door | | |
| doorlock-lockstate | Switch | Door Lock/Latch State | Locks and unlocks the door and maintains the lock state. If the door lock supports unbolting, this will reflect the latched state. | Door | | |
| doorlock-boltstate | Switch | Door Bolt State | Bolts and unbolts the door and maintains the bolt state. | Door | | |
| fancontrol-fanmode | Number | Fan Mode | Set the fan mode | HVAC | | |
| onoffcontrol-onoff | Switch | Switch | Switches the power on and off | Light | | |
| levelcontrol-level | Dimmer | Dimmer | Sets the level of the light | Light | | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ public class MatterBindingConstants {
public static final String CHANNEL_ID_DOORLOCK_STATE = "doorlock-lockstate";
public static final ChannelTypeUID CHANNEL_DOORLOCK_STATE = new ChannelTypeUID(BINDING_ID,
CHANNEL_ID_DOORLOCK_STATE);
public static final String CHANNEL_ID_DOORLOCK_BOLTSTATE = "doorlock-boltstate";
public static final ChannelTypeUID CHANNEL_DOORLOCK_BOLTSTATE = new ChannelTypeUID(BINDING_ID,
CHANNEL_ID_DOORLOCK_BOLTSTATE);
public static final String CHANNEL_ID_WINDOWCOVERING_LIFT = "windowcovering-lift";
public static final ChannelTypeUID CHANNEL_WINDOWCOVERING_LIFT = new ChannelTypeUID(BINDING_ID,
CHANNEL_ID_WINDOWCOVERING_LIFT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
*/
package org.openhab.binding.matter.internal.controller.devices.converter;

import static org.openhab.binding.matter.internal.MatterBindingConstants.CHANNEL_DOORLOCK_BOLTSTATE;
import static org.openhab.binding.matter.internal.MatterBindingConstants.CHANNEL_DOORLOCK_STATE;
import static org.openhab.binding.matter.internal.MatterBindingConstants.CHANNEL_ID_DOORLOCK_BOLTSTATE;
import static org.openhab.binding.matter.internal.MatterBindingConstants.CHANNEL_ID_DOORLOCK_STATE;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand All @@ -32,6 +34,7 @@
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.UnDefType;

/**
* A converter for translating {@link DoorLockCluster} events and attributes to openHAB channels and back again.
Expand All @@ -48,19 +51,37 @@ public DoorLockConverter(DoorLockCluster cluster, MatterBaseThingHandler handler

@Override
public Map<Channel, @Nullable StateDescription> createChannels(ChannelGroupUID channelGroupUID) {
Map<Channel, @Nullable StateDescription> channels = new HashMap<>();
Channel channel = ChannelBuilder
.create(new ChannelUID(channelGroupUID, CHANNEL_ID_DOORLOCK_STATE), CoreItemFactory.SWITCH)
.withType(CHANNEL_DOORLOCK_STATE).build();
channels.put(channel, null);
if (initializingCluster.featureMap.unbolting) {
Channel boltChannel = ChannelBuilder
.create(new ChannelUID(channelGroupUID, CHANNEL_ID_DOORLOCK_BOLTSTATE), CoreItemFactory.SWITCH)
.withType(CHANNEL_DOORLOCK_BOLTSTATE).build();
channels.put(boltChannel, null);
}

return Collections.singletonMap(channel, null);
return channels;
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof OnOffType onOffType) {
ClusterCommand doorLockCommand = onOffType == OnOffType.ON ? DoorLockCluster.lockDoor(null)
: DoorLockCluster.unlockDoor(null);
handler.sendClusterCommand(endpointNumber, DoorLockCluster.CLUSTER_NAME, doorLockCommand);
String id = channelUID.getIdWithoutGroup();
switch (id) {
case CHANNEL_ID_DOORLOCK_STATE:
ClusterCommand doorLockCommand = onOffType == OnOffType.ON ? DoorLockCluster.lockDoor(null)
: DoorLockCluster.unlockDoor(null);
handler.sendClusterCommand(endpointNumber, DoorLockCluster.CLUSTER_NAME, doorLockCommand);
break;
case CHANNEL_ID_DOORLOCK_BOLTSTATE:
ClusterCommand boltCommand = onOffType == OnOffType.ON ? DoorLockCluster.lockDoor(null)
: DoorLockCluster.unboltDoor(null);
handler.sendClusterCommand(endpointNumber, DoorLockCluster.CLUSTER_NAME, boltCommand);
break;
}
}
super.handleCommand(channelUID, command);
}
Expand All @@ -70,8 +91,7 @@ public void onEvent(AttributeChangedMessage message) {
switch (message.path.attributeName) {
case "lockState":
if (message.value instanceof DoorLockCluster.LockStateEnum lockState) {
updateState(CHANNEL_ID_DOORLOCK_STATE,
lockState == DoorLockCluster.LockStateEnum.LOCKED ? OnOffType.ON : OnOffType.OFF);
updateLockState(lockState);
}
default:
break;
Expand All @@ -81,7 +101,39 @@ public void onEvent(AttributeChangedMessage message) {

@Override
public void initState() {
updateState(CHANNEL_ID_DOORLOCK_STATE,
initializingCluster.lockState == DoorLockCluster.LockStateEnum.LOCKED ? OnOffType.ON : OnOffType.OFF);
updateLockState(initializingCluster.lockState);
}

private void updateLockState(DoorLockCluster.LockStateEnum lockState) {
switch (lockState) {
case LOCKED:
// both the lock and bolt state are locked
updateState(CHANNEL_ID_DOORLOCK_STATE, OnOffType.ON);
if (initializingCluster.featureMap.unbolting) {
updateState(CHANNEL_ID_DOORLOCK_BOLTSTATE, OnOffType.ON);
}
break;
case UNLOCKED:
// both the lock and bolt state are unlocked
updateState(CHANNEL_ID_DOORLOCK_STATE, OnOffType.OFF);
if (initializingCluster.featureMap.unbolting) {
updateState(CHANNEL_ID_DOORLOCK_BOLTSTATE, OnOffType.OFF);
}
break;
case UNLATCHED:
// the lock state is locked (latched), but the bolt state is unlocked
updateState(CHANNEL_ID_DOORLOCK_STATE, OnOffType.ON);
if (initializingCluster.featureMap.unbolting) {
updateState(CHANNEL_ID_DOORLOCK_BOLTSTATE, OnOffType.OFF);
}
break;
case NOT_FULLY_LOCKED:
// we don't know the state of the lock or bolt, something is wrong
updateState(CHANNEL_ID_DOORLOCK_STATE, UnDefType.UNDEF);
if (initializingCluster.featureMap.unbolting) {
updateState(CHANNEL_ID_DOORLOCK_BOLTSTATE, UnDefType.UNDEF);
}
break;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ channel-type.matter.colorcontrol-temperature-abs.label = Color Temperature
channel-type.matter.colorcontrol-temperature-abs.description = Sets the color temperature of the light in mirek
channel-type.matter.colorcontrol-temperature.label = Color Temperature
channel-type.matter.colorcontrol-temperature.description = Sets the color temperature of the light
channel-type.matter.doorlock-boltstate.label = Door Bolt State
channel-type.matter.doorlock-boltstate.description = Bolts and unbolts the door and maintains the bolt state.
channel-type.matter.doorlock-lockstate.label = Door Lock State
channel-type.matter.doorlock-lockstate.description = Locks and unlocks the door and maintains the lock state
channel-type.matter.doorlock-lockstate.description = Locks and unlocks the door and maintains the lock state. If the door lock supports unbolting, this will reflect the latched state.
channel-type.matter.electricalenergymeasurement-energymeasurmement-energy.label = Energy
channel-type.matter.electricalpowermeasurement-activecurrent.label = Active Current
channel-type.matter.electricalpowermeasurement-activepower.label = Active Power
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,19 @@
<channel-type id="doorlock-lockstate">
<item-type>Switch</item-type>
<label>Door Lock State</label>
<description>Locks and unlocks the door and maintains the lock state</description>
<description>Locks and unlocks the door and maintains the lock state. If the door lock supports unbolting, this will
reflect the latched state.</description>
<tags>
<tag>Status</tag>
<tag>OpenState</tag>
</tags>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>

<channel-type id="doorlock-boltstate">
<item-type>Switch</item-type>
<label>Door Bolt State</label>
<description>Bolts and unbolts the door and maintains the bolt state.</description>
<tags>
<tag>Status</tag>
<tag>OpenState</tag>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.binding.matter.internal.controller.devices.converter;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand All @@ -32,6 +33,7 @@
import org.openhab.core.thing.ChannelGroupUID;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.UnDefType;

/**
* Test class for DoorLockConverter
Expand All @@ -56,7 +58,9 @@ void setUp() {
}

@Test
void testCreateChannels() {
void testCreateStandardChannels() {
mockCluster.featureMap = new DoorLockCluster.FeatureMap(false, false, false, false, false, false, false, false,
false, false, false, false, false);
ChannelGroupUID channelGroupUID = new ChannelGroupUID("matter:node:test:12345:1");
Map<Channel, @Nullable StateDescription> channels = converter.createChannels(channelGroupUID);
assertEquals(1, channels.size());
Expand All @@ -65,6 +69,21 @@ void testCreateChannels() {
assertEquals("Switch", channel.getAcceptedItemType());
}

@Test
void testCreateChannelsWithUnbolting() {
mockCluster.featureMap = new DoorLockCluster.FeatureMap(false, false, false, false, false, false, false, false,
false, false, true, false, false);
ChannelGroupUID channelGroupUID = new ChannelGroupUID("matter:node:test:12345:1");
Map<Channel, @Nullable StateDescription> channels = converter.createChannels(channelGroupUID);
assertEquals(2, channels.size());
boolean hasLockState = channels.keySet().stream()
.anyMatch(c -> c.getUID().toString().equals("matter:node:test:12345:1#doorlock-lockstate"));
boolean hasBoltState = channels.keySet().stream()
.anyMatch(c -> c.getUID().toString().equals("matter:node:test:12345:1#doorlock-boltstate"));
assertTrue(hasLockState);
assertTrue(hasBoltState);
}

@Test
void testHandleCommandLock() {
ChannelUID channelUID = new ChannelUID("matter:node:test:12345:1#doorlock-lockstate");
Expand All @@ -81,20 +100,76 @@ void testHandleCommandUnlock() {
eq(DoorLockCluster.unlockDoor(null)));
}

@Test
void testHandleBoltCommands() {
ChannelUID boltChannelUID = new ChannelUID("matter:node:test:12345:1#doorlock-boltstate");
converter.handleCommand(boltChannelUID, OnOffType.ON);
verify(mockHandler, times(1)).sendClusterCommand(eq(1), eq(DoorLockCluster.CLUSTER_NAME),
eq(DoorLockCluster.lockDoor(null)));
converter.handleCommand(boltChannelUID, OnOffType.OFF);
verify(mockHandler, times(1)).sendClusterCommand(eq(1), eq(DoorLockCluster.CLUSTER_NAME),
eq(DoorLockCluster.unboltDoor(null)));
}

@Test
void testOnEventWithLockState() {
mockCluster.featureMap = new DoorLockCluster.FeatureMap(false, false, false, false, false, false, false, false,
false, false, true, false, false);
AttributeChangedMessage message = new AttributeChangedMessage();
message.path = new Path();
message.path.attributeName = "lockState";
message.value = DoorLockCluster.LockStateEnum.LOCKED;
converter.onEvent(message);
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-lockstate"), eq(OnOffType.ON));
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-boltstate"), eq(OnOffType.ON));
}

@Test
void testOnEventWithLockStateUnboltingUnlocked() {
mockCluster.featureMap = new DoorLockCluster.FeatureMap(false, false, false, false, false, false, false, false,
false, false, true, false, false);
AttributeChangedMessage message = new AttributeChangedMessage();
message.path = new Path();
message.path.attributeName = "lockState";
message.value = DoorLockCluster.LockStateEnum.UNLOCKED;
converter.onEvent(message);
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-lockstate"), eq(OnOffType.OFF));
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-boltstate"), eq(OnOffType.OFF));
}

@Test
void testOnEventWithLockStateUnboltingUnlatched() {
mockCluster.featureMap = new DoorLockCluster.FeatureMap(false, false, false, false, false, false, false, false,
false, false, true, false, false);
AttributeChangedMessage message = new AttributeChangedMessage();
message.path = new Path();
message.path.attributeName = "lockState";
message.value = DoorLockCluster.LockStateEnum.UNLATCHED;
converter.onEvent(message);
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-lockstate"), eq(OnOffType.ON));
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-boltstate"), eq(OnOffType.OFF));
}

@Test
void testOnEventWithLockStateUnboltingNotFullyLocked() {
mockCluster.featureMap = new DoorLockCluster.FeatureMap(false, false, false, false, false, false, false, false,
false, false, true, false, false);
AttributeChangedMessage message = new AttributeChangedMessage();
message.path = new Path();
message.path.attributeName = "lockState";
message.value = DoorLockCluster.LockStateEnum.NOT_FULLY_LOCKED;
converter.onEvent(message);
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-lockstate"), eq(UnDefType.UNDEF));
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-boltstate"), eq(UnDefType.UNDEF));
}

@Test
void testInitState() {
mockCluster.featureMap = new DoorLockCluster.FeatureMap(false, false, false, false, false, false, false, false,
false, false, true, false, false);
mockCluster.lockState = DoorLockCluster.LockStateEnum.LOCKED;
converter.initState();
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-lockstate"), eq(OnOffType.ON));
verify(mockHandler, times(1)).updateState(eq(1), eq("doorlock-boltstate"), eq(OnOffType.ON));
}
}