[boschshc] Provide alarm channel for smoke detectors

* add new channel definition for alarm channel in thing-types.xml
* add alarm channel to Smoke Detector and Smoke Detector II
* add update instruction sets in binding.xml
* re-generate i18n properties file
* add constant for new channel
* implement alarm service
* register service package in tests
* extend abstract smoke detector handler
* add unit tests
* add documentation

Signed-off-by: David Pace <dev@davidpace.de>
David Pace 2025-01-26 21:31:47 +01:00
parent dfea7a13cf
commit 5f7531c820
13 changed files with 325 additions and 33 deletions

View File

@ -296,9 +296,12 @@ The smoke detector warns you in case of fire.
**Thing Type ID**: `smoke-detector` **Thing Type ID**: `smoke-detector`
| Channel Type ID | Item Type | Writable | Description | | Channel Type ID | Item Type | Writable | Description |
| ------------------ | -------------------- | :------: | ------------------------------------------------------------------------------------------------- | | ----------------| ----------| :------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| smoke-check | String | &#9745; | State of the smoke check. Also used to request a new smoke check. | | alarm | String | &#9745; | Alarm state of the smoke detector. Possible values are: `IDLE_OFF`, `PRIMARY_ALARM`, `SECONDARY_ALARM` and `INTRUSION_ALARM`. |
| smoke-check | String | &#9745; | State of the smoke check. Also used to request a new smoke check. |
| battery-level | Number | &#9744; | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. |
| low-battery | Switch | &#9744; | Indicates whether the battery is low (`ON`) or OK (`OFF`). |
### Smoke Detector II ### Smoke Detector II
@ -306,12 +309,13 @@ The smoke detector warns you in case of fire.
**Thing Type ID**: `smoke-detector` **Thing Type ID**: `smoke-detector`
| Channel Type ID | Item Type | Writable | Description | | Channel Type ID | Item Type | Writable | Description |
|-------------------|-------------| :------: |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------------|-----------| :------: |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| smoke-check | String | &#9745; | State of the smoke check. Also used to request a new smoke check. | | alarm | String | &#9745; | Alarm state of the smoke detector. Possible values are: `IDLE_OFF`, `PRIMARY_ALARM`, `SECONDARY_ALARM` and `INTRUSION_ALARM`. |
| battery-level | Number | &#9744; | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. | | smoke-check | String | &#9745; | State of the smoke check. Also used to request a new smoke check. |
| low-battery | Switch | &#9744; | Indicates whether the battery is low (`ON`) or OK (`OFF`). | | battery-level | Number | &#9744; | Current battery level percentage as integer number. Bosch-specific battery levels are mapped to numbers as follows: `OK`: 100, `LOW_BATTERY`: 10, `CRITICAL_LOW`: 1, `CRITICALLY_LOW_BATTERY`: 1, `NOT_AVAILABLE`: `UNDEF`. |
| signal-strength | Number | &#9744; | Communication quality between the device and the Smart Home Controller. Possible values range between 0 (unknown) and 4 (best signal strength). | | low-battery | Switch | &#9744; | Indicates whether the battery is low (`ON`) or OK (`OFF`). |
| signal-strength | Number | &#9744; | Communication quality between the device and the Smart Home Controller. Possible values range between 0 (unknown) and 4 (best signal strength). |
### User-defined States ### User-defined States

View File

@ -81,10 +81,10 @@ public class BoschShcCommandExtension extends AbstractConsoleCommandExtension im
* <code>src/main/java/org/openhab/binding/boschshc/internal/services</code>. * <code>src/main/java/org/openhab/binding/boschshc/internal/services</code>.
*/ */
List<String> getAllBoschShcServices() { List<String> getAllBoschShcServices() {
return List.of("airqualitylevel", "batterylevel", "binaryswitch", "bypass", "cameranotification", "childlock", return List.of("airqualitylevel", "alarm", "batterylevel", "binaryswitch", "bypass", "cameranotification",
"childprotection", "communicationquality", "hsbcoloractuator", "humiditylevel", "illuminance", "childlock", "childprotection", "communicationquality", "hsbcoloractuator", "humiditylevel",
"impulseswitch", "intrusion", "keypad", "latestmotion", "multilevelswitch", "powermeter", "powerswitch", "illuminance", "impulseswitch", "intrusion", "keypad", "latestmotion", "multilevelswitch", "powermeter",
"privacymode", "roomclimatecontrol", "shuttercontact", "shuttercontrol", "silentmode", "powerswitch", "privacymode", "roomclimatecontrol", "shuttercontact", "shuttercontrol", "silentmode",
"smokedetectorcheck", "temperaturelevel", "userstate", "valvetappet", "waterleakagesensor", "smokedetectorcheck", "temperaturelevel", "userstate", "valvetappet", "waterleakagesensor",
"waterleakagesensorcheck", "waterleakagesensortilt"); "waterleakagesensorcheck", "waterleakagesensortilt");
} }

View File

@ -12,12 +12,15 @@
*/ */
package org.openhab.binding.boschshc.internal.devices; package org.openhab.binding.boschshc.internal.devices;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ALARM;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK; import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK;
import java.util.List; import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.alarm.AlarmService;
import org.openhab.binding.boschshc.internal.services.alarm.dto.AlarmServiceState;
import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.SmokeDetectorCheckService; import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.SmokeDetectorCheckService;
import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.dto.SmokeDetectorCheckServiceState; import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.dto.SmokeDetectorCheckServiceState;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
@ -34,10 +37,12 @@ import org.openhab.core.types.Command;
@NonNullByDefault @NonNullByDefault
public abstract class AbstractSmokeDetectorHandler extends AbstractBatteryPoweredDeviceHandler { public abstract class AbstractSmokeDetectorHandler extends AbstractBatteryPoweredDeviceHandler {
private AlarmService alarmService;
private SmokeDetectorCheckService smokeDetectorCheckService; private SmokeDetectorCheckService smokeDetectorCheckService;
protected AbstractSmokeDetectorHandler(Thing thing) { protected AbstractSmokeDetectorHandler(Thing thing) {
super(thing); super(thing);
this.alarmService = new AlarmService();
this.smokeDetectorCheckService = new SmokeDetectorCheckService(); this.smokeDetectorCheckService = new SmokeDetectorCheckService();
} }
@ -45,6 +50,7 @@ public abstract class AbstractSmokeDetectorHandler extends AbstractBatteryPowere
protected void initializeServices() throws BoschSHCException { protected void initializeServices() throws BoschSHCException {
super.initializeServices(); super.initializeServices();
this.registerService(alarmService, this::updateChannels, List.of(CHANNEL_ALARM));
this.registerService(smokeDetectorCheckService, this::updateChannels, List.of(CHANNEL_SMOKE_CHECK)); this.registerService(smokeDetectorCheckService, this::updateChannels, List.of(CHANNEL_SMOKE_CHECK));
} }
@ -53,12 +59,19 @@ public abstract class AbstractSmokeDetectorHandler extends AbstractBatteryPowere
super.handleCommand(channelUID, command); super.handleCommand(channelUID, command);
switch (channelUID.getId()) { switch (channelUID.getId()) {
case CHANNEL_ALARM:
this.handleServiceCommand(this.alarmService, command);
break;
case CHANNEL_SMOKE_CHECK: case CHANNEL_SMOKE_CHECK:
this.handleServiceCommand(this.smokeDetectorCheckService, command); this.handleServiceCommand(this.smokeDetectorCheckService, command);
break; break;
} }
} }
private void updateChannels(AlarmServiceState state) {
updateState(CHANNEL_ALARM, new StringType(state.value.toString()));
}
private void updateChannels(SmokeDetectorCheckServiceState state) { private void updateChannels(SmokeDetectorCheckServiceState state) {
updateState(CHANNEL_SMOKE_CHECK, new StringType(state.value.toString())); updateState(CHANNEL_SMOKE_CHECK, new StringType(state.value.toString()));
} }

View File

@ -116,6 +116,7 @@ public class BoschSHCBindingConstants {
public static final String CHANNEL_IMPULSE_SWITCH = "impulse-switch"; public static final String CHANNEL_IMPULSE_SWITCH = "impulse-switch";
public static final String CHANNEL_IMPULSE_LENGTH = "impulse-length"; public static final String CHANNEL_IMPULSE_LENGTH = "impulse-length";
public static final String CHANNEL_INSTANT_OF_LAST_IMPULSE = "instant-of-last-impulse"; public static final String CHANNEL_INSTANT_OF_LAST_IMPULSE = "instant-of-last-impulse";
public static final String CHANNEL_ALARM = "alarm";
// numbered channels // numbered channels
// the rationale for introducing numbered channels was discussed in // the rationale for introducing numbered channels was discussed in
// https://github.com/openhab/openhab-addons/pull/16400 // https://github.com/openhab/openhab-addons/pull/16400

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2010-2025 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.boschshc.internal.services.alarm;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
import org.openhab.binding.boschshc.internal.services.alarm.dto.AlarmServiceState;
import org.openhab.binding.boschshc.internal.services.alarm.dto.AlarmState;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
/**
* Alarm service for smoke detectors.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
public class AlarmService extends BoschSHCService<AlarmServiceState> {
public AlarmService() {
super("Alarm", AlarmServiceState.class);
}
@Override
public AlarmServiceState handleCommand(Command command) throws BoschSHCException {
if (command instanceof StringType stringCommand) {
AlarmServiceState state = new AlarmServiceState();
state.value = AlarmState.from(stringCommand.toFullString());
return state;
}
return super.handleCommand(command);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2010-2025 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.boschshc.internal.services.alarm.dto;
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
/**
* State for alarm services of smoke detectors.
*
* @author David Pace - Initial contribution
*
*/
public class AlarmServiceState extends BoschSHCServiceState {
public AlarmServiceState() {
super("alarmState");
}
public AlarmState value;
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2025 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.boschshc.internal.services.alarm.dto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Possible states of smoke detector alarms.
*
* @author David Pace - Initial contribution
*
*/
public enum AlarmState {
IDLE_OFF,
PRIMARY_ALARM,
SECONDARY_ALARM,
INTRUSION_ALARM;
private static final Logger LOGGER = LoggerFactory.getLogger(AlarmState.class);
public static AlarmState from(String identifier) {
try {
return valueOf(identifier);
} catch (IllegalArgumentException e) {
LOGGER.warn("Unsupported alarm state: {}", identifier);
return IDLE_OFF;
}
}
}

View File

@ -84,6 +84,12 @@ channel-type.boschshc.alarm-state.state.option.PRE_ALARM = Alarm is about to go
channel-type.boschshc.alarm-state.state.option.ALARM_ON = Alarm was triggered channel-type.boschshc.alarm-state.state.option.ALARM_ON = Alarm was triggered
channel-type.boschshc.alarm-state.state.option.ALARM_MUTED = Alarm is muted channel-type.boschshc.alarm-state.state.option.ALARM_MUTED = Alarm is muted
channel-type.boschshc.alarm-state.state.option.UNKNOWN = Alarm status is unknown channel-type.boschshc.alarm-state.state.option.UNKNOWN = Alarm status is unknown
channel-type.boschshc.alarm.label = Alarm
channel-type.boschshc.alarm.description = Alarm state of the smoke detector.
channel-type.boschshc.alarm.state.option.IDLE_OFF = Alarm off
channel-type.boschshc.alarm.state.option.PRIMARY_ALARM = Primary alarm
channel-type.boschshc.alarm.state.option.SECONDARY_ALARM = Secondary alarm
channel-type.boschshc.alarm.state.option.INTRUSION_ALARM = Intrusion alarm
channel-type.boschshc.arm-action.label = Arm Action channel-type.boschshc.arm-action.label = Arm Action
channel-type.boschshc.arm-action.description = Arms the intrusion detection system using the given profile ID. channel-type.boschshc.arm-action.description = Arms the intrusion detection system using the given profile ID.
channel-type.boschshc.arming-state.label = Arming State channel-type.boschshc.arming-state.label = Arming State

View File

@ -344,6 +344,7 @@
<description>The smoke detector warns you in case of fire.</description> <description>The smoke detector warns you in case of fire.</description>
<channels> <channels>
<channel id="alarm" typeId="alarm"/>
<channel id="smoke-check" typeId="smoke-check"/> <channel id="smoke-check" typeId="smoke-check"/>
<channel id="battery-level" typeId="system.battery-level"/> <channel id="battery-level" typeId="system.battery-level"/>
<channel id="low-battery" typeId="system.low-battery"/> <channel id="low-battery" typeId="system.low-battery"/>
@ -412,6 +413,7 @@
<description>The smoke detector warns you in case of fire.</description> <description>The smoke detector warns you in case of fire.</description>
<channels> <channels>
<channel id="alarm" typeId="alarm"/>
<channel id="smoke-check" typeId="smoke-check"/> <channel id="smoke-check" typeId="smoke-check"/>
<channel id="battery-level" typeId="system.battery-level"/> <channel id="battery-level" typeId="system.battery-level"/>
<channel id="low-battery" typeId="system.low-battery"/> <channel id="low-battery" typeId="system.low-battery"/>
@ -887,4 +889,19 @@
<state readOnly="true"/> <state readOnly="true"/>
</channel-type> </channel-type>
<channel-type id="alarm">
<item-type>String</item-type>
<label>Alarm</label>
<description>Alarm state of the smoke detector.</description>
<category>Alarm</category>
<state readOnly="false">
<options>
<option value="IDLE_OFF">Alarm off</option>
<option value="PRIMARY_ALARM">Primary alarm</option>
<option value="SECONDARY_ALARM">Secondary alarm</option>
<option value="INTRUSION_ALARM">Intrusion alarm</option>
</options>
</state>
</channel-type>
</thing:thing-descriptions> </thing:thing-descriptions>

View File

@ -22,4 +22,20 @@
</instruction-set> </instruction-set>
</thing-type> </thing-type>
<thing-type uid="boschshc:smoke-detector">
<instruction-set targetVersion="1">
<add-channel id="alarm">
<type>boschshc:alarm</type>
</add-channel>
</instruction-set>
</thing-type>
<thing-type uid="boschshc:smoke-detector-2">
<instruction-set targetVersion="1">
<add-channel id="alarm">
<type>boschshc:alarm</type>
</add-channel>
</instruction-set>
</thing-type>
</update:update-descriptions> </update:update-descriptions>

View File

@ -14,7 +14,10 @@ package org.openhab.binding.boschshc.internal.devices;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_ALARM;
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@ -24,7 +27,8 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Captor; import org.mockito.Captor;
import org.openhab.binding.boschshc.internal.devices.smokedetector.SmokeDetectorHandler; import org.openhab.binding.boschshc.internal.devices.smokedetector.SmokeDetectorHandler;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException; import org.openhab.binding.boschshc.internal.services.alarm.dto.AlarmServiceState;
import org.openhab.binding.boschshc.internal.services.alarm.dto.AlarmState;
import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.SmokeDetectorCheckState; import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.SmokeDetectorCheckState;
import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.dto.SmokeDetectorCheckServiceState; import org.openhab.binding.boschshc.internal.services.smokedetectorcheck.dto.SmokeDetectorCheckServiceState;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
@ -52,32 +56,34 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
@Captor @Captor
private @NonNullByDefault({}) ArgumentCaptor<SmokeDetectorCheckServiceState> smokeDetectorCheckStateCaptor; private @NonNullByDefault({}) ArgumentCaptor<SmokeDetectorCheckServiceState> smokeDetectorCheckStateCaptor;
@Captor
private @NonNullByDefault({}) ArgumentCaptor<AlarmServiceState> alarmStateCaptor;
@Test @Test
public void testHandleCommand() public void testHandleCommandSmokeTest() throws InterruptedException, TimeoutException, ExecutionException {
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
// valid commands with valid thing & channel // valid commands with valid thing & channel
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK), getFixture().handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SMOKE_CHECK),
new StringType(SmokeDetectorCheckState.SMOKE_TEST_REQUESTED.toString())); new StringType(SmokeDetectorCheckState.SMOKE_TEST_REQUESTED.toString()));
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"), verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"),
smokeDetectorCheckStateCaptor.capture()); smokeDetectorCheckStateCaptor.capture());
SmokeDetectorCheckServiceState state = smokeDetectorCheckStateCaptor.getValue(); SmokeDetectorCheckServiceState state = smokeDetectorCheckStateCaptor.getValue();
assertSame(SmokeDetectorCheckState.SMOKE_TEST_REQUESTED, state.value); assertSame(SmokeDetectorCheckState.SMOKE_TEST_REQUESTED, state.value);
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK), getFixture().handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SMOKE_CHECK),
new StringType(SmokeDetectorCheckState.NONE.toString())); new StringType(SmokeDetectorCheckState.NONE.toString()));
verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"), verify(getBridgeHandler(), times(2)).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"),
smokeDetectorCheckStateCaptor.capture()); smokeDetectorCheckStateCaptor.capture());
state = smokeDetectorCheckStateCaptor.getValue(); state = smokeDetectorCheckStateCaptor.getValue();
assertSame(SmokeDetectorCheckState.NONE, state.value); assertSame(SmokeDetectorCheckState.NONE, state.value);
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK), getFixture().handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SMOKE_CHECK),
new StringType(SmokeDetectorCheckState.SMOKE_TEST_OK.toString())); new StringType(SmokeDetectorCheckState.SMOKE_TEST_OK.toString()));
verify(getBridgeHandler(), times(3)).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"), verify(getBridgeHandler(), times(3)).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"),
smokeDetectorCheckStateCaptor.capture()); smokeDetectorCheckStateCaptor.capture());
state = smokeDetectorCheckStateCaptor.getValue(); state = smokeDetectorCheckStateCaptor.getValue();
assertSame(SmokeDetectorCheckState.SMOKE_TEST_OK, state.value); assertSame(SmokeDetectorCheckState.SMOKE_TEST_OK, state.value);
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK), getFixture().handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SMOKE_CHECK),
new StringType(SmokeDetectorCheckState.SMOKE_TEST_FAILED.toString())); new StringType(SmokeDetectorCheckState.SMOKE_TEST_FAILED.toString()));
verify(getBridgeHandler(), times(4)).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"), verify(getBridgeHandler(), times(4)).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"),
smokeDetectorCheckStateCaptor.capture()); smokeDetectorCheckStateCaptor.capture());
@ -86,10 +92,8 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
} }
@Test @Test
public void testHandleCommandPlayPauseType() public void testHandleCommandPlayPauseType() throws InterruptedException, TimeoutException, ExecutionException {
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException { getFixture().handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SMOKE_CHECK), PlayPauseType.PLAY);
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK),
PlayPauseType.PLAY);
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"), verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("SmokeDetectorCheck"),
smokeDetectorCheckStateCaptor.capture()); smokeDetectorCheckStateCaptor.capture());
SmokeDetectorCheckServiceState state = smokeDetectorCheckStateCaptor.getValue(); SmokeDetectorCheckServiceState state = smokeDetectorCheckStateCaptor.getValue();
@ -97,10 +101,8 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
} }
@Test @Test
public void testHandleCommandUnknownCommand() public void testHandleCommandUnknownCommand() {
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException { getFixture().handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_SMOKE_CHECK), OnOffType.ON);
getFixture().handleCommand(new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK),
OnOffType.ON);
ThingStatusInfo expectedThingStatusInfo = ThingStatusInfoBuilder ThingStatusInfo expectedThingStatusInfo = ThingStatusInfoBuilder
.create(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR) .create(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR)
.withDescription( .withDescription(
@ -113,8 +115,7 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
public void testUpdateChannelSmokeDetectorCheckServiceStateNone() { public void testUpdateChannelSmokeDetectorCheckServiceStateNone() {
JsonElement jsonObject = JsonParser.parseString("{\"@type\":\"smokeDetectorCheckState\",\"value\":NONE}"); JsonElement jsonObject = JsonParser.parseString("{\"@type\":\"smokeDetectorCheckState\",\"value\":NONE}");
getFixture().processUpdate("SmokeDetectorCheck", jsonObject); getFixture().processUpdate("SmokeDetectorCheck", jsonObject);
verify(getCallback()).stateUpdated( verify(getCallback()).stateUpdated(new ChannelUID(getThing().getUID(), CHANNEL_SMOKE_CHECK),
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK),
new StringType("NONE")); new StringType("NONE"));
} }
@ -123,8 +124,38 @@ public abstract class AbstractSmokeDetectorHandlerTest<T extends AbstractSmokeDe
JsonElement jsonObject = JsonParser JsonElement jsonObject = JsonParser
.parseString("{\"@type\":\"smokeDetectorCheckState\",\"value\":SMOKE_TEST_REQUESTED}"); .parseString("{\"@type\":\"smokeDetectorCheckState\",\"value\":SMOKE_TEST_REQUESTED}");
getFixture().processUpdate("SmokeDetectorCheck", jsonObject); getFixture().processUpdate("SmokeDetectorCheck", jsonObject);
verify(getCallback()).stateUpdated( verify(getCallback()).stateUpdated(new ChannelUID(getThing().getUID(), CHANNEL_SMOKE_CHECK),
new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_SMOKE_CHECK),
new StringType("SMOKE_TEST_REQUESTED")); new StringType("SMOKE_TEST_REQUESTED"));
} }
@Test
public void testUpdateChannelsAlarm() {
String json = """
{
"@type": "alarmState",
"value": IDLE_OFF
}
""";
JsonElement jsonObject = JsonParser.parseString(json);
getFixture().processUpdate("Alarm", jsonObject);
verify(getCallback()).stateUpdated(new ChannelUID(getThing().getUID(), CHANNEL_ALARM),
new StringType("IDLE_OFF"));
}
@Test
public void testHandleCommandAlarm() throws InterruptedException, TimeoutException, ExecutionException {
getFixture().handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_ALARM), new StringType("PRIMARY_ALARM"));
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("Alarm"), alarmStateCaptor.capture());
AlarmServiceState state = alarmStateCaptor.getValue();
assertSame(AlarmState.PRIMARY_ALARM, state.value);
}
@Test
public void testHandleCommandAlarmUnknownAlarmState()
throws InterruptedException, TimeoutException, ExecutionException {
getFixture().handleCommand(new ChannelUID(getThing().getUID(), CHANNEL_ALARM), new StringType("INVALID"));
verify(getBridgeHandler()).putState(eq(getDeviceID()), eq("Alarm"), alarmStateCaptor.capture());
AlarmServiceState state = alarmStateCaptor.getValue();
assertSame(AlarmState.IDLE_OFF, state.value);
}
} }

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2010-2025 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.boschshc.internal.services.alarm;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
import org.openhab.binding.boschshc.internal.services.alarm.dto.AlarmServiceState;
import org.openhab.binding.boschshc.internal.services.alarm.dto.AlarmState;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
/**
* Unit tests for {@link AlarmService}.
*
* @author David Pace - Initial contribution
*/
@NonNullByDefault
class AlarmServiceTest {
private @NonNullByDefault({}) AlarmService fixture;
@BeforeEach
public void beforeEach() {
fixture = new AlarmService();
}
@Test
void testHandleCommandValidCommand() throws BoschSHCException {
AlarmServiceState state = fixture.handleCommand(new StringType("IDLE_OFF"));
assertNotNull(state);
assertSame(AlarmState.IDLE_OFF, state.value);
}
@Test
void testHandleCommandInvalidCommand() {
assertThrows(BoschSHCException.class, () -> fixture.handleCommand(new DecimalType(0)));
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2010-2025 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.boschshc.internal.services.alarm.dto;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link AlarmState}.
*
* @author David Pace - Initial contribution
*
*/
@NonNullByDefault
class AlarmStateTest {
@Test
void testFromValidIdentifier() {
assertSame(AlarmState.PRIMARY_ALARM, AlarmState.from("PRIMARY_ALARM"));
}
@Test
void testFromInvalidIdentifier() {
assertSame(AlarmState.IDLE_OFF, AlarmState.from("INVALID"));
}
}