mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[boschshc] Handle relay mode changes during initialization (#17160)
* [boschshc] Handle relay mode changes during initialization Signed-off-by: David Pace <dev@davidpace.de> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
476a37544a
commit
ed9afe8fbe
@ -235,9 +235,11 @@ The smart switching relay is your universal all-rounder for smart switching.
|
||||
| child-protection | Switch | ☑ | Indicates whether the child protection is active. |
|
||||
| power-switch | Switch | ☑ | Switches the relay on or off. Only available if the relay is in power switch mode. |
|
||||
| impulse-switch | Switch | ☑ | Channel to send impulses by means of `ON` events. After the time specified by `impulse-length`, the relay will switch off automatically and the state will be reset to `OFF`. Only available if the relay is in impulse switch mode. |
|
||||
| impulse-length | Number:Time | ☑ | Channel to configure how long the relay will stay on after receiving an impulse switch event. The time is specified in tenth seconds (deciseconds), e.g. 15 means 1.5 seconds. Only available if the relay is in impulse switch mode. |
|
||||
| impulse-length | Number:Time | ☑ | Channel to configure how long the relay will stay on after receiving an impulse switch event. If raw numbers (without time unit) are provided, the default unit is tenth seconds (deciseconds), e.g. 15 means 1.5 seconds. If quantities with time units are provided, the quantity will be converted to deciseconds internally, discarding any fraction digits that are more precise than expressible in whole deciseconds (e.g. 1.58 seconds will be converted to 15 ds). Only available if the relay is in impulse switch mode. |
|
||||
| instant-of-last-impulse | DateTime | ☐ | Timestamp indicating when the last impulse was triggered. Only available if the relay is in impulse switch mode. |
|
||||
|
||||
If the device mode is changed from power switch to impulse switch mode or vice versa, the corresponding thing has to be deleted and re-added in openHAB.
|
||||
|
||||
### Security Camera 360
|
||||
|
||||
Indoor security camera with 360° view and motion detection.
|
||||
|
@ -12,7 +12,6 @@
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.relay;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.BINDING_ID;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_CHILD_PROTECTION;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_IMPULSE_LENGTH;
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_IMPULSE_SWITCH;
|
||||
@ -23,8 +22,11 @@ import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConst
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -37,17 +39,19 @@ import org.openhab.binding.boschshc.internal.services.communicationquality.Commu
|
||||
import org.openhab.binding.boschshc.internal.services.communicationquality.dto.CommunicationQualityServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.impulseswitch.ImpulseSwitchService;
|
||||
import org.openhab.binding.boschshc.internal.services.impulseswitch.dto.ImpulseSwitchServiceState;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchService;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
@ -79,6 +83,13 @@ public class RelayHandler extends AbstractPowerSwitchHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(RelayHandler.class);
|
||||
|
||||
protected static final String PROPERTY_MODE = "mode";
|
||||
|
||||
/**
|
||||
* Unit for the impulse length, which is specified in deciseconds (tenth seconds)
|
||||
*/
|
||||
private static final Unit<Time> UNIT_DECISECOND = MetricPrefix.DECI(Units.SECOND);
|
||||
|
||||
private ChildProtectionService childProtectionService;
|
||||
private ImpulseSwitchService impulseSwitchService;
|
||||
|
||||
@ -108,10 +119,21 @@ public class RelayHandler extends AbstractPowerSwitchHandler {
|
||||
@Override
|
||||
protected boolean processDeviceInfo(Device deviceInfo) {
|
||||
this.isInImpulseSwitchMode = isRelayInImpulseSwitchMode(deviceInfo);
|
||||
configureChannels();
|
||||
boolean isChannelConfigurationValid = configureChannels();
|
||||
if (!isChannelConfigurationValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
updateModePropertyIfApplicable();
|
||||
return super.processDeviceInfo(deviceInfo);
|
||||
}
|
||||
|
||||
private void updateModePropertyIfApplicable() {
|
||||
String modePropertyValue = isInImpulseSwitchMode ? ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME
|
||||
: PowerSwitchService.POWER_SWITCH_SERVICE_NAME;
|
||||
updateProperty(PROPERTY_MODE, modePropertyValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically configures the channels according to the device mode.
|
||||
* <p>
|
||||
@ -123,94 +145,54 @@ public class RelayHandler extends AbstractPowerSwitchHandler {
|
||||
* then switches off automatically)</li>
|
||||
* </ul>
|
||||
*/
|
||||
private void configureChannels() {
|
||||
if (isInImpulseSwitchMode) {
|
||||
configureImpulseSwitchModeChannels();
|
||||
} else {
|
||||
configurePowerSwitchModeChannels();
|
||||
}
|
||||
private boolean configureChannels() {
|
||||
return isInImpulseSwitchMode ? configureImpulseSwitchModeChannels() : configurePowerSwitchModeChannels();
|
||||
}
|
||||
|
||||
private void configureImpulseSwitchModeChannels() {
|
||||
private boolean configureImpulseSwitchModeChannels() {
|
||||
List<String> channelsToBePresent = List.of(CHANNEL_IMPULSE_SWITCH, CHANNEL_IMPULSE_LENGTH,
|
||||
CHANNEL_INSTANT_OF_LAST_IMPULSE);
|
||||
List<String> channelsToBeAbsent = List.of(CHANNEL_POWER_SWITCH);
|
||||
configureChannels(channelsToBePresent, channelsToBeAbsent);
|
||||
return configureChannels(channelsToBePresent, channelsToBeAbsent);
|
||||
}
|
||||
|
||||
private void configurePowerSwitchModeChannels() {
|
||||
private boolean configurePowerSwitchModeChannels() {
|
||||
List<String> channelsToBePresent = List.of(CHANNEL_POWER_SWITCH);
|
||||
List<String> channelsToBeAbsent = List.of(CHANNEL_IMPULSE_SWITCH, CHANNEL_IMPULSE_LENGTH,
|
||||
CHANNEL_INSTANT_OF_LAST_IMPULSE);
|
||||
configureChannels(channelsToBePresent, channelsToBeAbsent);
|
||||
return configureChannels(channelsToBePresent, channelsToBeAbsent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-configures the channels of the associated thing, if applicable.
|
||||
*
|
||||
* @param channelsToBePresent channels to be added, if not present already
|
||||
* @param channelsToBePresent channels expected to be present according to the current device mode
|
||||
* @param channelsToBeAbsent channels to be removed, if present
|
||||
*
|
||||
* @return <code>true</code> if the channels were reconfigured or no re-configuration is necessary,
|
||||
* <code>false</code> if the thing has to be re-created manually
|
||||
*/
|
||||
private void configureChannels(List<String> channelsToBePresent, List<String> channelsToBeAbsent) {
|
||||
List<String> channelsToAdd = channelsToBePresent.stream().filter(c -> getThing().getChannel(c) == null)
|
||||
.toList();
|
||||
private boolean configureChannels(List<String> channelsToBePresent, List<String> channelsToBeAbsent) {
|
||||
Optional<String> anyChannelMissing = channelsToBePresent.stream().filter(c -> getThing().getChannel(c) == null)
|
||||
.findAny();
|
||||
|
||||
if (anyChannelMissing.isPresent()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error.relay-recreation-required");
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Channel> channelsToRemove = channelsToBeAbsent.stream().map(c -> getThing().getChannel(c))
|
||||
.filter(Objects::nonNull).map(Objects::requireNonNull).toList();
|
||||
|
||||
if (channelsToAdd.isEmpty() && channelsToRemove.isEmpty()) {
|
||||
return;
|
||||
if (channelsToRemove.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
if (!channelsToAdd.isEmpty()) {
|
||||
addChannels(channelsToAdd, thingBuilder);
|
||||
}
|
||||
if (!channelsToRemove.isEmpty()) {
|
||||
thingBuilder.withoutChannels(channelsToRemove);
|
||||
}
|
||||
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
|
||||
private void addChannels(List<String> channelsToAdd, ThingBuilder thingBuilder) {
|
||||
for (String channelToAdd : channelsToAdd) {
|
||||
Channel channel = createChannel(channelToAdd);
|
||||
thingBuilder.withChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
private Channel createChannel(String channelId) {
|
||||
ChannelUID channelUID = new ChannelUID(getThing().getUID(), channelId);
|
||||
ChannelTypeUID channelTypeUID = getChannelTypeUID(channelId);
|
||||
@Nullable
|
||||
String itemType = getItemType(channelId);
|
||||
return ChannelBuilder.create(channelUID, itemType).withType(channelTypeUID).build();
|
||||
}
|
||||
|
||||
private ChannelTypeUID getChannelTypeUID(String channelId) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_IMPULSE_SWITCH, CHANNEL_IMPULSE_LENGTH, CHANNEL_INSTANT_OF_LAST_IMPULSE:
|
||||
return new ChannelTypeUID(BINDING_ID, channelId);
|
||||
case CHANNEL_POWER_SWITCH:
|
||||
return DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_POWER;
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot determine channel type UID to create channel " + channelId + " dynamically.");
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String getItemType(String channelId) {
|
||||
switch (channelId) {
|
||||
case CHANNEL_POWER_SWITCH, CHANNEL_IMPULSE_SWITCH:
|
||||
return CoreItemFactory.SWITCH;
|
||||
case CHANNEL_IMPULSE_LENGTH:
|
||||
return CoreItemFactory.NUMBER + ":Time";
|
||||
case CHANNEL_INSTANT_OF_LAST_IMPULSE:
|
||||
return CoreItemFactory.DATETIME;
|
||||
default:
|
||||
throw new UnsupportedOperationException(
|
||||
"Cannot determine item type to create channel " + channelId + " dynamically.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isRelayInImpulseSwitchMode(Device deviceInfo) {
|
||||
@ -263,8 +245,8 @@ public class RelayHandler extends AbstractPowerSwitchHandler {
|
||||
updateChildProtectionState(onOffCommand);
|
||||
} else if (CHANNEL_IMPULSE_SWITCH.equals(channelUID.getId()) && command instanceof OnOffType onOffCommand) {
|
||||
triggerImpulse(onOffCommand);
|
||||
} else if (CHANNEL_IMPULSE_LENGTH.equals(channelUID.getId()) && command instanceof DecimalType number) {
|
||||
updateImpulseLength(number);
|
||||
} else if (CHANNEL_IMPULSE_LENGTH.equals(channelUID.getId())) {
|
||||
updateImpulseLength(command);
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,10 +270,15 @@ public class RelayHandler extends AbstractPowerSwitchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateImpulseLength(DecimalType number) {
|
||||
private void updateImpulseLength(Command command) {
|
||||
Integer impulseLength = getImpulseLength(command);
|
||||
if (impulseLength == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImpulseSwitchServiceState newState = cloneCurrentImpulseSwitchServiceState();
|
||||
if (newState != null) {
|
||||
newState.impulseLength = number.intValue();
|
||||
newState.impulseLength = impulseLength;
|
||||
this.currentImpulseSwitchServiceState = newState;
|
||||
logger.debug("New impulse length setting for relay: {} deciseconds", newState.impulseLength);
|
||||
|
||||
@ -300,6 +287,18 @@ public class RelayHandler extends AbstractPowerSwitchHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Integer getImpulseLength(Command command) {
|
||||
if (command instanceof DecimalType decimalCommand) {
|
||||
return decimalCommand.intValue();
|
||||
} else if (command instanceof QuantityType<?> quantityCommand) {
|
||||
@Nullable
|
||||
QuantityType<?> convertedQuantity = quantityCommand.toUnit(UNIT_DECISECOND);
|
||||
return convertedQuantity != null ? convertedQuantity.intValue() : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable ImpulseSwitchServiceState cloneCurrentImpulseSwitchServiceState() {
|
||||
if (currentImpulseSwitchServiceState != null) {
|
||||
ImpulseSwitchServiceState clonedState = new ImpulseSwitchServiceState();
|
||||
|
@ -220,3 +220,4 @@ offline.conf-error.invalid-device-id = Device ID is invalid.
|
||||
offline.conf-error.empty-state-id = No ID set.
|
||||
offline.conf-error.invalid-state-id = ID is invalid.
|
||||
offline.conf-error.child-device-ids-not-obtainable = Could not obtain child device IDs.
|
||||
offline.conf-error.relay-recreation-required = Relay mode (power/impulse switch) change detected. Please delete and re-create this Thing.
|
||||
|
@ -13,7 +13,9 @@
|
||||
package org.openhab.binding.boschshc.internal.devices;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@ -21,6 +23,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
@ -45,14 +48,15 @@ public abstract class AbstractBatteryPoweredDeviceHandlerTest<T extends Abstract
|
||||
|
||||
@BeforeEach
|
||||
@Override
|
||||
public void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
public void beforeEach(TestInfo testInfo)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
DeviceServiceData deviceServiceData = new DeviceServiceData();
|
||||
deviceServiceData.path = "/devices/hdm:ZigBee:000d6f0004b93361/services/BatteryLevel";
|
||||
deviceServiceData.id = "BatteryLevel";
|
||||
deviceServiceData.deviceId = "hdm:ZigBee:000d6f0004b93361";
|
||||
when(getBridgeHandler().getServiceData(anyString(), anyString())).thenReturn(deviceServiceData);
|
||||
|
||||
super.beforeEach();
|
||||
super.beforeEach(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -28,6 +28,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@ -72,8 +73,19 @@ public abstract class AbstractBoschSHCHandlerTest<T extends BoschSHCHandler> {
|
||||
this.fixture = createFixture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the fixture and all required mocks around the handler.
|
||||
*
|
||||
* @param testInfo used in subclasses where initializing the handler differently in individual tests is required.
|
||||
*
|
||||
* @throws InterruptedException
|
||||
* @throws TimeoutException
|
||||
* @throws ExecutionException
|
||||
* @throws BoschSHCException
|
||||
*/
|
||||
@BeforeEach
|
||||
void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
void beforeEach(TestInfo testInfo)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
fixture = createFixture();
|
||||
lenient().when(thing.getUID()).thenReturn(getThingUID());
|
||||
when(thing.getBridgeUID()).thenReturn(new ThingUID("boschshc", "shc", "myBridgeUID"));
|
||||
@ -86,7 +98,29 @@ public abstract class AbstractBoschSHCHandlerTest<T extends BoschSHCHandler> {
|
||||
configureDevice(device);
|
||||
lenient().when(bridgeHandler.getDeviceInfo(anyString())).thenReturn(device);
|
||||
|
||||
beforeHandlerInitialization(testInfo);
|
||||
|
||||
fixture.initialize();
|
||||
|
||||
afterHandlerInitialization(testInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to allow tests to add custom setup code before the handler initialization.
|
||||
*
|
||||
* @param testInfo provides metadata related to the current test being executed
|
||||
*/
|
||||
protected void beforeHandlerInitialization(TestInfo testInfo) {
|
||||
// default implementation is empty, subclasses may override
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to allow tests to add custom setup code after the handler initialization.
|
||||
*
|
||||
* @param testInfo provides metadata related to the current test being executed
|
||||
*/
|
||||
protected void afterHandlerInitialization(TestInfo testInfo) {
|
||||
// default implementation is empty, subclasses may override
|
||||
}
|
||||
|
||||
protected abstract T createFixture();
|
||||
|
@ -18,6 +18,7 @@ import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@ -28,6 +29,7 @@ import java.util.concurrent.TimeoutException;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@ -59,13 +61,14 @@ public abstract class AbstractPowerSwitchHandlerTest<T extends AbstractPowerSwit
|
||||
|
||||
@BeforeEach
|
||||
@Override
|
||||
public void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
public void beforeEach(TestInfo testInfo)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
PowerSwitchServiceState powerSwitchServiceState = new PowerSwitchServiceState();
|
||||
powerSwitchServiceState.switchState = PowerSwitchState.ON;
|
||||
when(getBridgeHandler().getState(anyString(), eq("PowerSwitch"), same(PowerSwitchServiceState.class)))
|
||||
lenient().when(getBridgeHandler().getState(anyString(), eq("PowerSwitch"), same(PowerSwitchServiceState.class)))
|
||||
.thenReturn(powerSwitchServiceState);
|
||||
|
||||
super.beforeEach();
|
||||
super.beforeEach(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -29,6 +29,7 @@ import javax.measure.quantity.Power;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
@ -56,14 +57,15 @@ public abstract class AbstractPowerSwitchHandlerWithPowerMeterTest<T extends Abs
|
||||
private @Captor @NonNullByDefault({}) ArgumentCaptor<QuantityType<Energy>> energyCaptor;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
public void beforeEach(TestInfo testInfo)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
PowerMeterServiceState powerMeterServiceState = new PowerMeterServiceState();
|
||||
powerMeterServiceState.powerConsumption = 12.34d;
|
||||
powerMeterServiceState.energyConsumption = 56.78d;
|
||||
lenient().when(getBridgeHandler().getState(anyString(), eq("PowerMeter"), same(PowerMeterServiceState.class)))
|
||||
.thenReturn(powerMeterServiceState);
|
||||
|
||||
super.beforeEach();
|
||||
super.beforeEach(testInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -12,22 +12,28 @@
|
||||
*/
|
||||
package org.openhab.binding.boschshc.internal.devices.relay;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.measure.quantity.Time;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.openhab.binding.boschshc.internal.devices.AbstractPowerSwitchHandlerTest;
|
||||
@ -36,11 +42,19 @@ import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.childprotection.dto.ChildProtectionServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.impulseswitch.ImpulseSwitchService;
|
||||
import org.openhab.binding.boschshc.internal.services.impulseswitch.dto.ImpulseSwitchServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.powerswitch.PowerSwitchService;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
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.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
@ -59,6 +73,61 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
|
||||
private @Captor @NonNullByDefault({}) ArgumentCaptor<ImpulseSwitchServiceState> impulseSwitchServiceStateCaptor;
|
||||
|
||||
@Override
|
||||
protected void beforeHandlerInitialization(TestInfo testInfo) {
|
||||
super.beforeHandlerInitialization(testInfo);
|
||||
|
||||
Channel signalStrengthChannel = ChannelBuilder
|
||||
.create(new ChannelUID(getThingUID(), BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH)).build();
|
||||
Channel childProtectionChannel = ChannelBuilder
|
||||
.create(new ChannelUID(getThingUID(), BoschSHCBindingConstants.CHANNEL_CHILD_PROTECTION)).build();
|
||||
Channel powerSwitchChannel = ChannelBuilder
|
||||
.create(new ChannelUID(getThingUID(), BoschSHCBindingConstants.CHANNEL_POWER_SWITCH)).build();
|
||||
Channel impulseSwitchChannel = ChannelBuilder
|
||||
.create(new ChannelUID(getThingUID(), BoschSHCBindingConstants.CHANNEL_IMPULSE_SWITCH)).build();
|
||||
Channel impulseLengthChannel = ChannelBuilder
|
||||
.create(new ChannelUID(getThingUID(), BoschSHCBindingConstants.CHANNEL_IMPULSE_LENGTH)).build();
|
||||
Channel instantOfLastImpulseChannel = ChannelBuilder
|
||||
.create(new ChannelUID(getThingUID(), BoschSHCBindingConstants.CHANNEL_INSTANT_OF_LAST_IMPULSE))
|
||||
.build();
|
||||
|
||||
when(getThing().getChannels()).thenReturn(List.of(signalStrengthChannel, childProtectionChannel,
|
||||
powerSwitchChannel, impulseSwitchChannel, impulseLengthChannel, instantOfLastImpulseChannel));
|
||||
|
||||
lenient().when(getThing().getChannel(BoschSHCBindingConstants.CHANNEL_SIGNAL_STRENGTH))
|
||||
.thenReturn(signalStrengthChannel);
|
||||
lenient().when(getThing().getChannel(BoschSHCBindingConstants.CHANNEL_CHILD_PROTECTION))
|
||||
.thenReturn(childProtectionChannel);
|
||||
lenient().when(getThing().getChannel(BoschSHCBindingConstants.CHANNEL_POWER_SWITCH))
|
||||
.thenReturn(powerSwitchChannel);
|
||||
lenient().when(getThing().getChannel(BoschSHCBindingConstants.CHANNEL_IMPULSE_SWITCH))
|
||||
.thenReturn(impulseSwitchChannel);
|
||||
lenient().when(getThing().getChannel(BoschSHCBindingConstants.CHANNEL_IMPULSE_LENGTH))
|
||||
.thenReturn(impulseLengthChannel);
|
||||
lenient().when(getThing().getChannel(BoschSHCBindingConstants.CHANNEL_INSTANT_OF_LAST_IMPULSE))
|
||||
.thenReturn(instantOfLastImpulseChannel);
|
||||
|
||||
if (testInfo.getTags().contains(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME)) {
|
||||
getDevice().deviceServiceIds = List.of(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterHandlerInitialization(TestInfo testInfo) {
|
||||
super.afterHandlerInitialization(testInfo);
|
||||
|
||||
@Nullable
|
||||
JsonElement impulseSwitchServiceState = JsonParser.parseString("""
|
||||
{
|
||||
"@type": "ImpulseSwitchState",
|
||||
"impulseState": false,
|
||||
"impulseLength": 100,
|
||||
"instantOfLastImpulse": "2024-04-14T15:52:31.677366Z"
|
||||
}
|
||||
""");
|
||||
getFixture().processUpdate(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME, impulseSwitchServiceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RelayHandler createFixture() {
|
||||
return new RelayHandler(getThing());
|
||||
@ -118,10 +187,10 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_CHILD_PROTECTION), OnOffType.ON);
|
||||
}
|
||||
|
||||
@Tag(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME)
|
||||
@Test
|
||||
void testUpdateChannelsImpulseSwitchService()
|
||||
throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException {
|
||||
configureImpulseSwitchMode();
|
||||
String json = """
|
||||
{
|
||||
"@type": "ImpulseSwitchState",
|
||||
@ -133,6 +202,7 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
JsonElement jsonObject = JsonParser.parseString(json);
|
||||
|
||||
getFixture().processUpdate("ImpulseSwitch", jsonObject);
|
||||
|
||||
verify(getCallback()).stateUpdated(
|
||||
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_IMPULSE_SWITCH), OnOffType.ON);
|
||||
verify(getCallback(), times(2)).stateUpdated(
|
||||
@ -143,10 +213,10 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
new DateTimeType("2024-04-14T15:52:31.677366Z"));
|
||||
}
|
||||
|
||||
@Tag(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME)
|
||||
@Test
|
||||
void testUpdateChannelsImpulseSwitchServiceNoInstantOfLastImpulse()
|
||||
throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException {
|
||||
configureImpulseSwitchMode();
|
||||
String json = """
|
||||
{
|
||||
"@type": "ImpulseSwitchState",
|
||||
@ -167,28 +237,33 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
UnDefType.NULL);
|
||||
}
|
||||
|
||||
private void configureImpulseSwitchMode()
|
||||
throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException {
|
||||
@Test
|
||||
void testDeviceModeChanged() throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException {
|
||||
getDevice().deviceServiceIds = List.of(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME);
|
||||
|
||||
// initialize again to check whether mode change is detected
|
||||
getFixture().initialize();
|
||||
|
||||
assertThat(getFixture().getThing().getChannel(BoschSHCBindingConstants.CHANNEL_IMPULSE_SWITCH),
|
||||
is(notNullValue()));
|
||||
assertThat(getFixture().getThing().getChannel(BoschSHCBindingConstants.CHANNEL_IMPULSE_LENGTH),
|
||||
is(notNullValue()));
|
||||
assertThat(getFixture().getThing().getChannel(BoschSHCBindingConstants.CHANNEL_INSTANT_OF_LAST_IMPULSE),
|
||||
is(notNullValue()));
|
||||
verify(getCallback()).statusUpdated(any(Thing.class),
|
||||
argThat(status -> status.getStatus().equals(ThingStatus.OFFLINE)
|
||||
&& status.getStatusDetail().equals(ThingStatusDetail.CONFIGURATION_ERROR)));
|
||||
|
||||
@Nullable
|
||||
JsonElement impulseSwitchServiceState = JsonParser.parseString("""
|
||||
{
|
||||
"@type": "ImpulseSwitchState",
|
||||
"impulseState": false,
|
||||
"impulseLength": 100,
|
||||
"instantOfLastImpulse": "2024-04-14T15:52:31.677366Z"
|
||||
verify(getCallback(), times(1)).statusUpdated(any(Thing.class),
|
||||
argThat(status -> status.getStatus().equals(ThingStatus.ONLINE)));
|
||||
|
||||
verify(getCallback(), times(0)).thingUpdated(
|
||||
argThat(t -> ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME.equals(t.getProperties().get("mode"))));
|
||||
}
|
||||
""");
|
||||
getFixture().processUpdate(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME, impulseSwitchServiceState);
|
||||
|
||||
@Test
|
||||
void testDeviceModeUnchanged()
|
||||
throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException {
|
||||
// initialize again without mode change
|
||||
getFixture().initialize();
|
||||
|
||||
verify(getCallback(), times(0)).statusUpdated(any(Thing.class),
|
||||
argThat(status -> status.getStatus().equals(ThingStatus.OFFLINE)
|
||||
&& status.getStatusDetail().equals(ThingStatusDetail.CONFIGURATION_ERROR)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -211,11 +286,10 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
verify(getBridgeHandler(), times(0)).putState(eq(getDeviceID()), eq("ChildProtection"), any());
|
||||
}
|
||||
|
||||
@Tag(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME)
|
||||
@Test
|
||||
void testHandleCommandImpulseStateOn()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
configureImpulseSwitchMode();
|
||||
|
||||
Instant testDate = Instant.now();
|
||||
getFixture().setCurrentDateTimeProvider(() -> testDate);
|
||||
|
||||
@ -229,11 +303,10 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
assertThat(state.instantOfLastImpulse, is(testDate.toString()));
|
||||
}
|
||||
|
||||
@Tag(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME)
|
||||
@Test
|
||||
void testHandleCommandImpulseLength()
|
||||
void testHandleCommandImpulseLengthDecimalType()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
configureImpulseSwitchMode();
|
||||
|
||||
Instant testDate = Instant.now();
|
||||
getFixture().setCurrentDateTimeProvider(() -> testDate);
|
||||
|
||||
@ -247,6 +320,41 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
assertThat(state.instantOfLastImpulse, is("2024-04-14T15:52:31.677366Z"));
|
||||
}
|
||||
|
||||
@Tag(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME)
|
||||
@Test
|
||||
void testHandleCommandImpulseLengthQuantityType()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
Instant testDate = Instant.now();
|
||||
getFixture().setCurrentDateTimeProvider(() -> testDate);
|
||||
|
||||
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_IMPULSE_LENGTH),
|
||||
new QuantityType<Time>(1.5, Units.SECOND));
|
||||
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME),
|
||||
impulseSwitchServiceStateCaptor.capture());
|
||||
ImpulseSwitchServiceState state = impulseSwitchServiceStateCaptor.getValue();
|
||||
assertThat(state.impulseState, is(false));
|
||||
assertThat(state.impulseLength, is(15));
|
||||
assertThat(state.instantOfLastImpulse, is("2024-04-14T15:52:31.677366Z"));
|
||||
}
|
||||
|
||||
@Tag(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME)
|
||||
@Test
|
||||
void testHandleCommandImpulseLengthQuantityTypeTooManyFractionDigits()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
Instant testDate = Instant.now();
|
||||
getFixture().setCurrentDateTimeProvider(() -> testDate);
|
||||
|
||||
// 0.08 s of 1.58 s will be discarded because API precision is limited to deciseconds
|
||||
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_IMPULSE_LENGTH),
|
||||
new QuantityType<Time>(1.58, Units.SECOND));
|
||||
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME),
|
||||
impulseSwitchServiceStateCaptor.capture());
|
||||
ImpulseSwitchServiceState state = impulseSwitchServiceStateCaptor.getValue();
|
||||
assertThat(state.impulseState, is(false));
|
||||
assertThat(state.impulseLength, is(15));
|
||||
assertThat(state.instantOfLastImpulse, is("2024-04-14T15:52:31.677366Z"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleCommandImpulseStateOff()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
@ -255,4 +363,17 @@ class RelayHandlerTest extends AbstractPowerSwitchHandlerTest<RelayHandler> {
|
||||
verify(getBridgeHandler(), times(0)).postState(eq(getDeviceID()),
|
||||
eq(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateModePropertyIfApplicablePowerSwitchMode() {
|
||||
verify(getCallback(), times(2)).thingUpdated(argThat(t -> PowerSwitchService.POWER_SWITCH_SERVICE_NAME
|
||||
.equals(t.getProperties().get(RelayHandler.PROPERTY_MODE))));
|
||||
}
|
||||
|
||||
@Tag(ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME)
|
||||
@Test
|
||||
void testUpdateModePropertyIfApplicableImpulseSwitchMode() {
|
||||
verify(getCallback(), times(2)).thingUpdated(argThat(t -> ImpulseSwitchService.IMPULSE_SWITCH_SERVICE_NAME
|
||||
.equals(t.getProperties().get(RelayHandler.PROPERTY_MODE))));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user