mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[boschshc] Add user defined states (#16028)
Signed-off-by: Patrick Gell <patgit023@gmail.com>
This commit is contained in:
parent
af13c9d133
commit
d620d261b7
@ -19,6 +19,7 @@ Binding for the Bosch Smart Home.
|
||||
- [Intrusion Detection System](#intrusion-detection-system)
|
||||
- [Smart Bulb](#smart-bulb)
|
||||
- [Smoke Detector](#smoke-detector)
|
||||
- [User-defined States](#user-defined-states)
|
||||
- [Limitations](#limitations)
|
||||
- [Discovery](#discovery)
|
||||
- [Bridge Configuration](#bridge-configuration)
|
||||
@ -218,6 +219,19 @@ The smoke detector warns you in case of fire.
|
||||
| smoke-check | String | ☑ | State of the smoke check. Also used to request a new smoke check. |
|
||||
|
||||
|
||||
### User-defined States
|
||||
|
||||
User-defined states enable automations to be better adapted to specific needs and everyday situations.
|
||||
Individual states can be activated/deactivated and can be used as triggers, conditions and actions in automations.
|
||||
|
||||
**Thing Type ID**: `user-defined-state`
|
||||
|
||||
|
||||
| Channel Type ID | Item Type | Writable | Description |
|
||||
|-----------------|-----------| :------: |--------------------------------------------|
|
||||
| user-state | Switch | ☑ | Switches the User-defined state on or off. |
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
No major limitation known.
|
||||
|
@ -49,6 +49,8 @@ public class BoschSHCBindingConstants {
|
||||
public static final ThingTypeUID THING_TYPE_SMART_BULB = new ThingTypeUID(BINDING_ID, "smart-bulb");
|
||||
public static final ThingTypeUID THING_TYPE_SMOKE_DETECTOR = new ThingTypeUID(BINDING_ID, "smoke-detector");
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_USER_DEFINED_STATE = new ThingTypeUID(BINDING_ID, "user-defined-state");
|
||||
|
||||
// List of all Channel IDs
|
||||
// Auto-generated from thing-types.xml via script, don't modify
|
||||
public static final String CHANNEL_SCENARIO_TRIGGERED = "scenario-triggered";
|
||||
@ -87,6 +89,8 @@ public class BoschSHCBindingConstants {
|
||||
public static final String CHANNEL_SILENT_MODE = "silent-mode";
|
||||
public static final String CHANNEL_ILLUMINANCE = "illuminance";
|
||||
|
||||
public static final String CHANNEL_USER_DEFINED_STATE = "user-state";
|
||||
|
||||
// static device/service names
|
||||
public static final String SERVICE_INTRUSION_DETECTION = "intrusionDetectionSystem";
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import org.openhab.binding.boschshc.internal.devices.smartbulb.SmartBulbHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.smokedetector.SmokeDetectorHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.thermostat.ThermostatHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.twinguard.TwinguardHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.userdefinedstate.UserStateHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.wallthermostat.WallThermostatHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.windowcontact.WindowContactHandler;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@ -82,7 +83,8 @@ public class BoschSHCHandlerFactory extends BaseThingHandlerFactory {
|
||||
new ThingTypeHandlerMapping(THING_TYPE_INTRUSION_DETECTION_SYSTEM, IntrusionDetectionHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_SMART_PLUG_COMPACT, PlugHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_SMART_BULB, SmartBulbHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_SMOKE_DETECTOR, SmokeDetectorHandler::new));
|
||||
new ThingTypeHandlerMapping(THING_TYPE_SMOKE_DETECTOR, SmokeDetectorHandler::new),
|
||||
new ThingTypeHandlerMapping(THING_TYPE_USER_DEFINED_STATE, UserStateHandler::new));
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
|
@ -38,6 +38,8 @@ import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.userstate.dto.UserStateServiceState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -131,6 +133,15 @@ public class BoschHttpClient extends HttpClient {
|
||||
return this.getBoschSmartHomeUrl(String.format("devices/%s/services/%s/state", deviceId, serviceName));
|
||||
}
|
||||
|
||||
public <T extends BoschSHCServiceState> String getServiceStateUrl(String serviceName, String deviceId,
|
||||
Class<T> serviceClass) {
|
||||
if (serviceClass.isAssignableFrom(UserStateServiceState.class)) {
|
||||
return this.getBoschSmartHomeUrl(String.format("userdefinedstates/%s/state", deviceId));
|
||||
} else {
|
||||
return getServiceStateUrl(serviceName, deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL to get general information about a service.
|
||||
* <p>
|
||||
@ -291,8 +302,13 @@ public class BoschHttpClient extends HttpClient {
|
||||
.timeout(10, TimeUnit.SECONDS); // Set default timeout
|
||||
|
||||
if (content != null) {
|
||||
String body = GsonUtils.DEFAULT_GSON_INSTANCE.toJson(content);
|
||||
logger.trace("create request for {} and content {}", url, content);
|
||||
final String body;
|
||||
if (content.getClass().isAssignableFrom(UserStateServiceState.class)) {
|
||||
body = ((UserStateServiceState) content).getStateAsString();
|
||||
} else {
|
||||
body = GsonUtils.DEFAULT_GSON_INSTANCE.toJson(content);
|
||||
}
|
||||
logger.trace("create request for {} and content {}", url, body);
|
||||
request = request.content(new StringContentProvider(body));
|
||||
} else {
|
||||
logger.trace("create request for {}", url);
|
||||
|
@ -41,6 +41,7 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceDat
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
|
||||
import org.openhab.binding.boschshc.internal.discovery.ThingDiscoveryService;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
|
||||
@ -80,6 +81,8 @@ import com.google.gson.reflect.TypeToken;
|
||||
@NonNullByDefault
|
||||
public class BridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private static final String HTTP_CLIENT_NOT_INITIALIZED = "HttpClient not initialized";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BridgeHandler.class);
|
||||
|
||||
/**
|
||||
@ -154,11 +157,11 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
|
||||
// Instantiate HttpClient with the SslContextFactory
|
||||
BoschHttpClient httpClient = this.httpClient = new BoschHttpClient(ipAddress, password, factory);
|
||||
BoschHttpClient localHttpClient = this.httpClient = new BoschHttpClient(ipAddress, password, factory);
|
||||
|
||||
// Start http client
|
||||
try {
|
||||
httpClient.start();
|
||||
localHttpClient.start();
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
String.format("Could not create http connection to controller: %s", e.getMessage()));
|
||||
@ -170,16 +173,16 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
// Initialize bridge in the background.
|
||||
// Start initial access the first time
|
||||
scheduleInitialAccess(httpClient);
|
||||
scheduleInitialAccess(localHttpClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// Cancel scheduled pairing.
|
||||
@Nullable
|
||||
ScheduledFuture<?> scheduledPairing = this.scheduledPairing;
|
||||
if (scheduledPairing != null) {
|
||||
scheduledPairing.cancel(true);
|
||||
ScheduledFuture<?> localScheduledPairing = this.scheduledPairing;
|
||||
if (localScheduledPairing != null) {
|
||||
localScheduledPairing.cancel(true);
|
||||
this.scheduledPairing = null;
|
||||
}
|
||||
|
||||
@ -187,10 +190,10 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
this.longPolling.stop();
|
||||
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient != null) {
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient != null) {
|
||||
try {
|
||||
httpClient.stop();
|
||||
localHttpClient.stop();
|
||||
} catch (Exception e) {
|
||||
logger.debug("HttpClient failed on bridge disposal: {}", e.getMessage(), e);
|
||||
}
|
||||
@ -295,16 +298,16 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
*/
|
||||
public boolean checkBridgeAccess() throws InterruptedException {
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
|
||||
if (httpClient == null) {
|
||||
if (localHttpClient == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Sending http request to BoschSHC to check access: {}", httpClient);
|
||||
String url = httpClient.getBoschSmartHomeUrl("devices");
|
||||
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();
|
||||
logger.debug("Sending http request to BoschSHC to check access: {}", localHttpClient);
|
||||
String url = localHttpClient.getBoschSmartHomeUrl("devices");
|
||||
ContentResponse contentResponse = localHttpClient.createRequest(url, GET).send();
|
||||
|
||||
// check HTTP status code
|
||||
if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
|
||||
@ -327,15 +330,15 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
*/
|
||||
public List<Device> getDevices() throws InterruptedException {
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
try {
|
||||
logger.trace("Sending http request to Bosch to request devices: {}", httpClient);
|
||||
String url = httpClient.getBoschSmartHomeUrl("devices");
|
||||
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();
|
||||
logger.trace("Sending http request to Bosch to request devices: {}", localHttpClient);
|
||||
String url = localHttpClient.getBoschSmartHomeUrl("devices");
|
||||
ContentResponse contentResponse = localHttpClient.createRequest(url, GET).send();
|
||||
|
||||
// check HTTP status code
|
||||
if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
|
||||
@ -357,6 +360,39 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public List<UserDefinedState> getUserStates() throws InterruptedException {
|
||||
@Nullable
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
logger.trace("Sending http request to Bosch to request user-defined states: {}", localHttpClient);
|
||||
String url = localHttpClient.getBoschSmartHomeUrl("userdefinedstates");
|
||||
ContentResponse contentResponse = localHttpClient.createRequest(url, GET).send();
|
||||
|
||||
// check HTTP status code
|
||||
if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
|
||||
logger.debug("Request devices failed with status code: {}", contentResponse.getStatus());
|
||||
return List.of();
|
||||
}
|
||||
|
||||
String content = contentResponse.getContentAsString();
|
||||
logger.trace("Request devices completed with success: {} - status code: {}", content,
|
||||
contentResponse.getStatus());
|
||||
|
||||
Type collectionType = new TypeToken<ArrayList<UserDefinedState>>() {
|
||||
}.getType();
|
||||
List<UserDefinedState> nullableUserStates = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content,
|
||||
collectionType);
|
||||
return Optional.ofNullable(nullableUserStates).orElse(Collections.emptyList());
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logger.debug("Request user-defined states failed because of {}!", e.getMessage(), e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of rooms from the Smart-Home controller
|
||||
*
|
||||
@ -365,12 +401,12 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
public List<Room> getRooms() throws InterruptedException {
|
||||
List<Room> emptyRooms = new ArrayList<>();
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient != null) {
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient != null) {
|
||||
try {
|
||||
logger.trace("Sending http request to Bosch to request rooms");
|
||||
String url = httpClient.getBoschSmartHomeUrl("rooms");
|
||||
ContentResponse contentResponse = httpClient.createRequest(url, GET).send();
|
||||
String url = localHttpClient.getBoschSmartHomeUrl("rooms");
|
||||
ContentResponse contentResponse = localHttpClient.createRequest(url, GET).send();
|
||||
|
||||
// check HTTP status code
|
||||
if (!HttpStatus.getCode(contentResponse.getStatus()).isSuccess()) {
|
||||
@ -426,6 +462,8 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
for (BoschSHCServiceState serviceState : result.result) {
|
||||
if (serviceState instanceof DeviceServiceData deviceServiceData) {
|
||||
handleDeviceServiceData(deviceServiceData);
|
||||
} else if (serviceState instanceof UserDefinedState userDefinedState) {
|
||||
handleUserDefinedState(userDefinedState);
|
||||
} else if (serviceState instanceof Scenario scenario) {
|
||||
final Channel channel = this.getThing().getChannel(BoschSHCBindingConstants.CHANNEL_SCENARIO_TRIGGERED);
|
||||
if (channel != null && isLinked(channel.getUID())) {
|
||||
@ -458,6 +496,24 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUserDefinedState(@Nullable UserDefinedState userDefinedState) {
|
||||
if (userDefinedState != null) {
|
||||
JsonElement state = GsonUtils.DEFAULT_GSON_INSTANCE.toJsonTree(userDefinedState.isState());
|
||||
|
||||
logger.debug("Got update for user-defined state {} with id {}: {}", userDefinedState.getName(),
|
||||
userDefinedState.getId(), state);
|
||||
|
||||
var stateId = userDefinedState.getId();
|
||||
if (stateId == null || state == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Got update for user-defined state {}", userDefinedState);
|
||||
|
||||
forwardStateToHandlers(userDefinedState, state, stateId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the actual state object from the given {@link DeviceServiceData} instance.
|
||||
* <p>
|
||||
@ -482,12 +538,18 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
/**
|
||||
* Tries to find handlers for the device with the given ID and forwards the received state to the handlers.
|
||||
*
|
||||
* @param deviceServiceData object representing updates received in long poll results
|
||||
* @param serviceData object representing updates received in long poll results
|
||||
* @param state the received state object as JSON element
|
||||
* @param updateDeviceId the ID of the device for which the state update was received
|
||||
*/
|
||||
private void forwardStateToHandlers(DeviceServiceData deviceServiceData, JsonElement state, String updateDeviceId) {
|
||||
private void forwardStateToHandlers(BoschSHCServiceState serviceData, JsonElement state, String updateDeviceId) {
|
||||
boolean handled = false;
|
||||
final String serviceId;
|
||||
if (serviceData instanceof UserDefinedState userState) {
|
||||
serviceId = userState.getId();
|
||||
} else {
|
||||
serviceId = ((DeviceServiceData) serviceData).id;
|
||||
}
|
||||
|
||||
Bridge bridge = this.getThing();
|
||||
for (Thing childThing : bridge.getThings()) {
|
||||
@ -502,9 +564,8 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
logger.debug("Registered device: {} - looking for {}", deviceId, updateDeviceId);
|
||||
|
||||
if (deviceId != null && updateDeviceId.equals(deviceId)) {
|
||||
logger.debug("Found child: {} - calling processUpdate (id: {}) with {}", handler,
|
||||
deviceServiceData.id, state);
|
||||
handler.processUpdate(deviceServiceData.id, state);
|
||||
logger.debug("Found child: {} - calling processUpdate (id: {}) with {}", handler, serviceId, state);
|
||||
handler.processUpdate(serviceId, state);
|
||||
}
|
||||
} else {
|
||||
logger.warn("longPoll: child handler for {} does not implement Bosch SHC handler", baseHandler);
|
||||
@ -526,8 +587,8 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
private void handleLongPollFailure(Throwable e) {
|
||||
logger.warn("Long polling failed, will try to reconnect", e);
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
"@text/offline.long-polling-failed.http-client-null");
|
||||
return;
|
||||
@ -535,36 +596,68 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
this.updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.UNKNOWN.NONE,
|
||||
"@text/offline.long-polling-failed.trying-to-reconnect");
|
||||
scheduleInitialAccess(httpClient);
|
||||
scheduleInitialAccess(localHttpClient);
|
||||
}
|
||||
|
||||
public Device getDeviceInfo(String deviceId)
|
||||
throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException {
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
throw new BoschSHCException("HTTP client not initialized");
|
||||
}
|
||||
|
||||
String url = httpClient.getBoschSmartHomeUrl(String.format("devices/%s", deviceId));
|
||||
Request request = httpClient.createRequest(url, GET);
|
||||
String url = localHttpClient.getBoschSmartHomeUrl(String.format("devices/%s", deviceId));
|
||||
Request request = localHttpClient.createRequest(url, GET);
|
||||
|
||||
return httpClient.sendRequest(request, Device.class, Device::isValid, (Integer statusCode, String content) -> {
|
||||
JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content,
|
||||
JsonRestExceptionResponse.class);
|
||||
if (errorResponse != null && JsonRestExceptionResponse.isValid(errorResponse)) {
|
||||
if (errorResponse.errorCode.equals(JsonRestExceptionResponse.ENTITY_NOT_FOUND)) {
|
||||
return new BoschSHCException("@text/offline.conf-error.invalid-device-id");
|
||||
} else {
|
||||
return new BoschSHCException(
|
||||
String.format("Request for info of device %s failed with status code %d and error code %s",
|
||||
return localHttpClient.sendRequest(request, Device.class, Device::isValid,
|
||||
(Integer statusCode, String content) -> {
|
||||
JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content,
|
||||
JsonRestExceptionResponse.class);
|
||||
if (errorResponse != null && JsonRestExceptionResponse.isValid(errorResponse)) {
|
||||
if (errorResponse.errorCode.equals(JsonRestExceptionResponse.ENTITY_NOT_FOUND)) {
|
||||
return new BoschSHCException("@text/offline.conf-error.invalid-device-id");
|
||||
} else {
|
||||
return new BoschSHCException(String.format(
|
||||
"Request for info of device %s failed with status code %d and error code %s",
|
||||
deviceId, errorResponse.statusCode, errorResponse.errorCode));
|
||||
}
|
||||
} else {
|
||||
return new BoschSHCException(String.format("Request for info of device %s failed with status code %d",
|
||||
deviceId, statusCode));
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return new BoschSHCException(String.format(
|
||||
"Request for info of device %s failed with status code %d", deviceId, statusCode));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public UserDefinedState getUserStateInfo(String stateId)
|
||||
throws BoschSHCException, InterruptedException, TimeoutException, ExecutionException {
|
||||
@Nullable
|
||||
BoschHttpClient locaHttpClient = this.httpClient;
|
||||
if (locaHttpClient == null) {
|
||||
throw new BoschSHCException("HTTP client not initialized");
|
||||
}
|
||||
|
||||
String url = locaHttpClient.getBoschSmartHomeUrl(String.format("userdefinedstates/%s", stateId));
|
||||
Request request = locaHttpClient.createRequest(url, GET);
|
||||
|
||||
return locaHttpClient.sendRequest(request, UserDefinedState.class, UserDefinedState::isValid,
|
||||
(Integer statusCode, String content) -> {
|
||||
JsonRestExceptionResponse errorResponse = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(content,
|
||||
JsonRestExceptionResponse.class);
|
||||
if (errorResponse != null && JsonRestExceptionResponse.isValid(errorResponse)) {
|
||||
if (errorResponse.errorCode.equals(JsonRestExceptionResponse.ENTITY_NOT_FOUND)) {
|
||||
return new BoschSHCException("@text/offline.conf-error.invalid-state-id");
|
||||
} else {
|
||||
return new BoschSHCException(String.format(
|
||||
"Request for info of user-defines state %s failed with status code %d and error code %s",
|
||||
stateId, errorResponse.statusCode, errorResponse.errorCode));
|
||||
}
|
||||
} else {
|
||||
return new BoschSHCException(
|
||||
String.format("Request for info of user-defined state %s failed with status code %d",
|
||||
stateId, statusCode));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -588,15 +681,15 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
public <T extends BoschSHCServiceState> @Nullable T getState(String deviceId, String stateName, Class<T> stateClass)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
logger.warn("HttpClient not initialized");
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
logger.warn(HTTP_CLIENT_NOT_INITIALIZED);
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = httpClient.getServiceStateUrl(stateName, deviceId);
|
||||
String url = localHttpClient.getServiceStateUrl(stateName, deviceId, stateClass);
|
||||
logger.debug("getState(): Requesting \"{}\" from Bosch: {} via {}", stateName, deviceId, url);
|
||||
return getState(httpClient, url, stateClass);
|
||||
return getState(localHttpClient, url, stateClass);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -614,15 +707,15 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
public <T extends BoschSHCServiceState> @Nullable T getState(String endpoint, Class<T> stateClass)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
logger.warn("HttpClient not initialized");
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
logger.warn(HTTP_CLIENT_NOT_INITIALIZED);
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = httpClient.getBoschSmartHomeUrl(endpoint);
|
||||
String url = localHttpClient.getBoschSmartHomeUrl(endpoint);
|
||||
logger.debug("getState(): Requesting from Bosch: {}", url);
|
||||
return getState(httpClient, url, stateClass);
|
||||
return getState(localHttpClient, url, stateClass);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -684,15 +777,15 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
public <T extends BoschSHCServiceState> @Nullable Response putState(String deviceId, String serviceName, T state)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
logger.warn("HttpClient not initialized");
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
logger.warn(HTTP_CLIENT_NOT_INITIALIZED);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create request
|
||||
String url = httpClient.getServiceStateUrl(serviceName, deviceId);
|
||||
Request request = httpClient.createRequest(url, PUT, state);
|
||||
String url = localHttpClient.getServiceStateUrl(serviceName, deviceId, state.getClass());
|
||||
Request request = localHttpClient.createRequest(url, PUT, state);
|
||||
|
||||
// Send request
|
||||
return request.send();
|
||||
@ -726,28 +819,28 @@ public class BridgeHandler extends BaseBridgeHandler {
|
||||
public <T extends BoschSHCServiceState> @Nullable Response postAction(String endpoint, @Nullable T requestBody)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
logger.warn("HttpClient not initialized");
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
logger.warn(HTTP_CLIENT_NOT_INITIALIZED);
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = httpClient.getBoschSmartHomeUrl(endpoint);
|
||||
Request request = httpClient.createRequest(url, POST, requestBody);
|
||||
String url = localHttpClient.getBoschSmartHomeUrl(endpoint);
|
||||
Request request = localHttpClient.createRequest(url, POST, requestBody);
|
||||
return request.send();
|
||||
}
|
||||
|
||||
public @Nullable DeviceServiceData getServiceData(String deviceId, String serviceName)
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
@Nullable
|
||||
BoschHttpClient httpClient = this.httpClient;
|
||||
if (httpClient == null) {
|
||||
logger.warn("HttpClient not initialized");
|
||||
BoschHttpClient localHttpClient = this.httpClient;
|
||||
if (localHttpClient == null) {
|
||||
logger.warn(HTTP_CLIENT_NOT_INITIALIZED);
|
||||
return null;
|
||||
}
|
||||
|
||||
String url = httpClient.getServiceUrl(serviceName, deviceId);
|
||||
String url = localHttpClient.getServiceUrl(serviceName, deviceId);
|
||||
logger.debug("getState(): Requesting \"{}\" from Bosch: {} via {}", serviceName, deviceId, url);
|
||||
return getState(httpClient, url, DeviceServiceData.class);
|
||||
return getState(localHttpClient, url, DeviceServiceData.class);
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ public class ScenarioHandler {
|
||||
}
|
||||
|
||||
public void triggerScenario(final BoschHttpClient httpClient, final String scenarioName) {
|
||||
|
||||
final Scenario[] scenarios;
|
||||
try {
|
||||
scenarios = getAvailableScenarios(httpClient);
|
||||
|
@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.devices.bridge.dto;
|
||||
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
|
||||
/**
|
||||
* Represents a single user-defined state defined on the Bosch Smart Home Controller.
|
||||
*
|
||||
* Example from Json:
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "@type": "userDefinedState",
|
||||
* "id": "23d34fa6-382a-444d-8aae-89c706e22158",
|
||||
* "name": "atHome",
|
||||
* "state": false
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Patrick Gell - Initial contribution
|
||||
*/
|
||||
public class UserDefinedState extends BoschSHCServiceState {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private boolean state;
|
||||
|
||||
public UserDefinedState() {
|
||||
super("UserDefinedState");
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(boolean state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserDefinedState{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", state=" + state + ", type='"
|
||||
+ type + '\'' + '}';
|
||||
}
|
||||
|
||||
public static Boolean isValid(UserDefinedState obj) {
|
||||
return obj != null && obj.id != null;
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.devices.userdefinedstate;
|
||||
|
||||
import static org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants.CHANNEL_USER_DEFINED_STATE;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCConfiguration;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCHandler;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.userstate.UserStateService;
|
||||
import org.openhab.binding.boschshc.internal.services.userstate.dto.UserStateServiceState;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
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.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* Handler for user defined states
|
||||
*
|
||||
* @author Patrick Gell - Initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UserStateHandler extends BoschSHCHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final UserStateService userStateService;
|
||||
/**
|
||||
* Bosch SHC configuration loaded from openHAB configuration.
|
||||
*/
|
||||
private @Nullable BoschSHCConfiguration config;
|
||||
|
||||
public UserStateHandler(Thing thing) {
|
||||
super(thing);
|
||||
|
||||
userStateService = new UserStateService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
var localConfig = this.config = getConfigAs(BoschSHCConfiguration.class);
|
||||
String stateId = localConfig.id;
|
||||
if (stateId == null || stateId.isBlank()) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.conf-error.empty-state-id");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to get state info to make sure the state exists
|
||||
try {
|
||||
var bridgeHandler = this.getBridgeHandler();
|
||||
var info = bridgeHandler.getUserStateInfo(stateId);
|
||||
logger.trace("User-defined state initialized:\n{}", info);
|
||||
} catch (TimeoutException | ExecutionException | BoschSHCException e) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getBoschID() {
|
||||
if (config != null) {
|
||||
return config.id;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeServices() throws BoschSHCException {
|
||||
super.initializeServices();
|
||||
|
||||
logger.debug("Initializing service for UserStateHandler");
|
||||
this.registerService(userStateService, this::updateChannels, List.of(CHANNEL_USER_DEFINED_STATE), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
super.handleCommand(channelUID, command);
|
||||
|
||||
if (channelUID.getId().equals(CHANNEL_USER_DEFINED_STATE) && (command instanceof OnOffType onOffCommand)) {
|
||||
updateUserState(channelUID.getThingUID().getId(), onOffCommand);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUserState(String stateId, OnOffType userState) {
|
||||
UserStateServiceState serviceState = new UserStateServiceState();
|
||||
serviceState.setState(userState == OnOffType.ON);
|
||||
try {
|
||||
getBridgeHandler().putState(stateId, "", serviceState);
|
||||
} catch (BoschSHCException | ExecutionException | TimeoutException e) {
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
String.format("Error while putting user-defined state for %s", stateId));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||
String.format("Error while putting user-defined state for %s", stateId));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChannels(UserStateServiceState userState) {
|
||||
super.updateState(CHANNEL_USER_DEFINED_STATE, userState.toOnOffType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processUpdate(String serviceName, @Nullable JsonElement stateData) {
|
||||
super.processUpdate("UserDefinedState", stateData);
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
@ -164,6 +165,8 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements T
|
||||
logger.debug("SHC has {} rooms", rooms.size());
|
||||
List<Device> devices = shcBridgeHandler.getDevices();
|
||||
logger.debug("SHC has {} devices", devices.size());
|
||||
List<UserDefinedState> userStates = shcBridgeHandler.getUserStates();
|
||||
logger.debug("SHC has {} user-defined states", userStates.size());
|
||||
|
||||
// Write found devices into openhab.log to support manual configuration
|
||||
for (Device d : devices) {
|
||||
@ -174,8 +177,47 @@ public class ThingDiscoveryService extends AbstractDiscoveryService implements T
|
||||
}
|
||||
}
|
||||
}
|
||||
for (UserDefinedState userState : userStates) {
|
||||
logger.debug("Found user-defined state: name={} id={} state={}", userState.getName(), userState.getId(),
|
||||
userState.isState());
|
||||
}
|
||||
|
||||
addDevices(devices, rooms);
|
||||
addUserStates(userStates);
|
||||
}
|
||||
|
||||
protected void addUserStates(List<UserDefinedState> userStates) {
|
||||
for (UserDefinedState userState : userStates) {
|
||||
addUserState(userState);
|
||||
}
|
||||
}
|
||||
|
||||
private void addUserState(UserDefinedState userState) {
|
||||
// see startScan for the runtime null check of shcBridgeHandler
|
||||
assert shcBridgeHandler != null;
|
||||
|
||||
logger.trace("Discovering user-defined state {}", userState.getName());
|
||||
logger.trace("- details: id {}, state {}", userState.getId(), userState.isState());
|
||||
|
||||
ThingTypeUID thingTypeUID = new ThingTypeUID(BoschSHCBindingConstants.BINDING_ID,
|
||||
BoschSHCBindingConstants.THING_TYPE_USER_DEFINED_STATE.getId());
|
||||
|
||||
logger.trace("- got thingTypeID '{}' for user-defined state '{}'", thingTypeUID.getId(), userState.getName());
|
||||
|
||||
ThingUID thingUID = new ThingUID(thingTypeUID, shcBridgeHandler.getThing().getUID(),
|
||||
userState.getId().replace(':', '_'));
|
||||
|
||||
logger.trace("- got thingUID '{}' for user-defined state: '{}'", thingUID, userState);
|
||||
|
||||
DiscoveryResultBuilder discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
|
||||
.withProperty("id", userState.getId()).withLabel(userState.getName());
|
||||
|
||||
discoveryResult.withBridge(shcBridgeHandler.getThing().getUID());
|
||||
|
||||
thingDiscovered(discoveryResult.build());
|
||||
|
||||
logger.debug("Discovered user-defined state '{}' with thingTypeUID={}, thingUID={}, id={}, state={}",
|
||||
userState.getName(), thingUID, thingTypeUID, userState.getId(), userState.isState());
|
||||
}
|
||||
|
||||
protected void addDevices(List<Device> devices, List<Room> rooms) {
|
||||
|
@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceData;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
@ -39,7 +40,6 @@ public class BoschServiceDataDeserializer implements JsonDeserializer<BoschSHCSe
|
||||
@Override
|
||||
public BoschSHCServiceState deserialize(JsonElement jsonElement, Type type,
|
||||
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
|
||||
|
||||
JsonObject jsonObject = jsonElement.getAsJsonObject();
|
||||
JsonElement dataType = jsonObject.get("@type");
|
||||
switch (dataType.getAsString()) {
|
||||
@ -58,6 +58,13 @@ public class BoschServiceDataDeserializer implements JsonDeserializer<BoschSHCSe
|
||||
scenario.lastTimeTriggered = jsonObject.get("lastTimeTriggered").getAsString();
|
||||
return scenario;
|
||||
}
|
||||
case "userDefinedState" -> {
|
||||
var state = new UserDefinedState();
|
||||
state.setId(jsonObject.get("id").getAsString());
|
||||
state.setName(jsonObject.get("name").getAsString());
|
||||
state.setState(jsonObject.get("state").getAsBoolean());
|
||||
return state;
|
||||
}
|
||||
default -> {
|
||||
return new BoschSHCServiceState(dataType.getAsString());
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ package org.openhab.binding.boschshc.internal.services.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
import org.openhab.binding.boschshc.internal.services.userstate.dto.UserStateServiceState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -62,9 +63,12 @@ public class BoschSHCServiceState {
|
||||
|
||||
public static <TState extends BoschSHCServiceState> @Nullable TState fromJson(String json,
|
||||
Class<TState> stateClass) {
|
||||
var state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass);
|
||||
var state = getUserDefinedStateOrNull(json, stateClass);
|
||||
if (state == null || !state.isValid()) {
|
||||
return null;
|
||||
state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass);
|
||||
if (state == null || !state.isValid()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
@ -72,11 +76,31 @@ public class BoschSHCServiceState {
|
||||
|
||||
public static <TState extends BoschSHCServiceState> @Nullable TState fromJson(JsonElement json,
|
||||
Class<TState> stateClass) {
|
||||
var state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass);
|
||||
var state = getUserDefinedStateOrNull(json, stateClass);
|
||||
if (state == null || !state.isValid()) {
|
||||
return null;
|
||||
state = GsonUtils.DEFAULT_GSON_INSTANCE.fromJson(json, stateClass);
|
||||
if (state == null || !state.isValid()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private static <TState extends BoschSHCServiceState> TState getUserDefinedStateOrNull(JsonElement json,
|
||||
Class<TState> stateClass) {
|
||||
if (stateClass.isAssignableFrom(UserStateServiceState.class)) {
|
||||
return BoschSHCServiceState.getUserDefinedStateOrNull(json.getAsString(), stateClass);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static <TState extends BoschSHCServiceState> TState getUserDefinedStateOrNull(String json,
|
||||
Class<TState> stateClass) {
|
||||
if (stateClass.isAssignableFrom(UserStateServiceState.class)) {
|
||||
var state = new UserStateServiceState();
|
||||
state.setStateFromString(json);
|
||||
return (TState) state;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.userstate;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.boschshc.internal.services.BoschSHCService;
|
||||
import org.openhab.binding.boschshc.internal.services.userstate.dto.UserStateServiceState;
|
||||
|
||||
/**
|
||||
* Service to get and set the state of a user-defined state.
|
||||
*
|
||||
* @author Patrick Gell - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UserStateService extends BoschSHCService<UserStateServiceState> {
|
||||
|
||||
public UserStateService() {
|
||||
super("UserDefinedState", UserStateServiceState.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.userstate.dto;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.openhab.binding.boschshc.internal.services.dto.BoschSHCServiceState;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
/**
|
||||
* Represents the state of a user-defined state
|
||||
*
|
||||
* @author Patrick Gell - Initial contribution
|
||||
*/
|
||||
public class UserStateServiceState extends BoschSHCServiceState {
|
||||
|
||||
public UserStateServiceState() {
|
||||
super("userdefinedstates");
|
||||
}
|
||||
|
||||
/**
|
||||
* Current state
|
||||
*/
|
||||
private boolean state;
|
||||
|
||||
public boolean isState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(boolean state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void setStateFromString(final String stateStr) {
|
||||
this.state = Boolean.parseBoolean(stateStr);
|
||||
}
|
||||
|
||||
public String getStateAsString() {
|
||||
return Boolean.toString(state);
|
||||
}
|
||||
|
||||
public @NonNull OnOffType toOnOffType() {
|
||||
return OnOffType.from(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UserStateServiceState{" + "state=" + state + ", type='" + type + '\'' + '}';
|
||||
}
|
||||
}
|
@ -21,4 +21,10 @@
|
||||
<description>Unique ID of the device.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
<config-description uri="thing-type:boschshc:user-defined-state">
|
||||
<parameter name="id" type="text" required="true">
|
||||
<label>State ID</label>
|
||||
<description>Unique ID of the state.</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</config-description:config-descriptions>
|
||||
|
@ -31,6 +31,8 @@ thing-type.boschshc.thermostat.label = Thermostat
|
||||
thing-type.boschshc.thermostat.description = Radiator thermostat
|
||||
thing-type.boschshc.twinguard.label = Twinguard
|
||||
thing-type.boschshc.twinguard.description = The Twinguard smoke detector warns you in case of fire and constantly monitors the air.
|
||||
thing-type.boschshc.user-defined-state.label = User-defined State
|
||||
thing-type.boschshc.user-defined-state.description = A User-defined state.
|
||||
thing-type.boschshc.wall-thermostat.label = Wall Thermostat
|
||||
thing-type.boschshc.wall-thermostat.description = Display of the current room temperature as well as the relative humidity in the room.
|
||||
thing-type.boschshc.window-contact.label = Door/Window Contact
|
||||
@ -44,6 +46,8 @@ thing-type.config.boschshc.bridge.password.label = System Password
|
||||
thing-type.config.boschshc.bridge.password.description = The system password of the Bosch Smart Home Controller necessary for pairing.
|
||||
thing-type.config.boschshc.device.id.label = Device ID
|
||||
thing-type.config.boschshc.device.id.description = Unique ID of the device.
|
||||
thing-type.config.boschshc.user-defined-state.id.label = State ID
|
||||
thing-type.config.boschshc.user-defined-state.id.description = Unique ID of the state.
|
||||
|
||||
# channel types
|
||||
|
||||
@ -130,6 +134,8 @@ channel-type.boschshc.temperature.label = Temperature
|
||||
channel-type.boschshc.temperature.description = Current measured temperature.
|
||||
channel-type.boschshc.trigger-scenario.label = Trigger Scenario
|
||||
channel-type.boschshc.trigger-scenario.description = Name of the scenario to trigger
|
||||
channel-type.boschshc.user-state.label = State
|
||||
channel-type.boschshc.user-state.description = State of user-defined state
|
||||
channel-type.boschshc.valve-tappet-position.label = Valve Tappet Position
|
||||
channel-type.boschshc.valve-tappet-position.description = Current open ratio (0 to 100).
|
||||
|
||||
@ -146,3 +152,5 @@ offline.long-polling-failed.trying-to-reconnect = Long polling failed, will try
|
||||
offline.interrupted = Connection to Bosch Smart Home Controller was interrupted.
|
||||
offline.conf-error.empty-device-id = No device ID set.
|
||||
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.
|
||||
|
@ -290,6 +290,21 @@
|
||||
<config-description-ref uri="thing-type:boschshc:device"/>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="user-defined-state">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="shc"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>User-defined State</label>
|
||||
<description>A User-defined state.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="user-state" typeId="user-state"/>
|
||||
</channels>
|
||||
|
||||
<config-description-ref uri="thing-type:boschshc:user-defined-state"/>
|
||||
</thing-type>
|
||||
|
||||
<!-- Channels -->
|
||||
|
||||
<channel-type id="system-availability">
|
||||
@ -553,4 +568,10 @@
|
||||
<description>Name of the scenario to trigger</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="user-state">
|
||||
<item-type>Switch</item-type>
|
||||
<label>State</label>
|
||||
<description>State of user-defined state</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
@ -37,6 +37,7 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.PairingFailedException;
|
||||
import org.openhab.binding.boschshc.internal.services.binaryswitch.dto.BinarySwitchServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.userstate.dto.UserStateServiceState;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
@ -94,6 +95,12 @@ class BoschHttpClientTest {
|
||||
httpClient.getServiceStateUrl("testService", "testDevice"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getServiceStateUrlForUserState() {
|
||||
assertEquals("https://127.0.0.1:8444/smarthome/userdefinedstates/testDevice/state",
|
||||
httpClient.getServiceStateUrl("testService", "testDevice", UserStateServiceState.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isAccessPossible() throws InterruptedException {
|
||||
assertFalse(httpClient.isAccessPossible());
|
||||
@ -165,6 +172,15 @@ class BoschHttpClientTest {
|
||||
|
||||
@Test
|
||||
void createRequestWithObject() {
|
||||
UserStateServiceState userState = new UserStateServiceState();
|
||||
userState.setState(true);
|
||||
Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, userState);
|
||||
assertNotNull(request);
|
||||
assertEquals("true", StandardCharsets.UTF_8.decode(request.getContent().iterator().next()).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createRequestForUserDefinedState() {
|
||||
BinarySwitchServiceState binarySwitchState = new BinarySwitchServiceState();
|
||||
binarySwitchState.on = true;
|
||||
Request request = httpClient.createRequest("https://127.0.0.1", HttpMethod.GET, binarySwitchState);
|
||||
|
@ -21,7 +21,9 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.BiFunction;
|
||||
@ -41,7 +43,10 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceDat
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceTest;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Faults;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedStateTest;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
import org.openhab.binding.boschshc.internal.services.binaryswitch.dto.BinarySwitchServiceState;
|
||||
import org.openhab.binding.boschshc.internal.services.intrusion.actions.arm.dto.ArmActionRequest;
|
||||
import org.openhab.binding.boschshc.internal.services.intrusion.dto.AlarmState;
|
||||
@ -243,6 +248,7 @@ class BridgeHandlerTest {
|
||||
void getDeviceState() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getServiceStateUrl(anyString(), anyString(), any())).thenCallRealMethod();
|
||||
when(httpClient.getServiceStateUrl(anyString(), anyString())).thenCallRealMethod();
|
||||
|
||||
Request request = mock(Request.class);
|
||||
@ -405,6 +411,7 @@ class BridgeHandlerTest {
|
||||
when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getServiceStateUrl(anyString(), anyString())).thenCallRealMethod();
|
||||
when(httpClient.getServiceStateUrl(anyString(), anyString(), any())).thenCallRealMethod();
|
||||
|
||||
Request request = mock(Request.class);
|
||||
when(request.header(anyString(), anyString())).thenReturn(request);
|
||||
@ -419,6 +426,78 @@ class BridgeHandlerTest {
|
||||
fixture.putState("hdm:ZigBee:f0d1b80000f2a3e9", "BinarySwitch", binarySwitchState);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserStateInfo() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
|
||||
String stateId = UUID.randomUUID().toString();
|
||||
|
||||
Request request = mock(Request.class);
|
||||
when(request.header(anyString(), anyString())).thenReturn(request);
|
||||
ContentResponse response = mock(ContentResponse.class);
|
||||
when(response.getStatus()).thenReturn(200);
|
||||
when(request.send()).thenReturn(response);
|
||||
when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
|
||||
when(httpClient.sendRequest(same(request), same(UserDefinedState.class), any(), any()))
|
||||
.thenReturn(UserDefinedStateTest.createTestState(stateId));
|
||||
|
||||
UserDefinedState userState = fixture.getUserStateInfo(stateId);
|
||||
assertEquals(stateId, userState.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserStates() throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
|
||||
String stateId = UUID.randomUUID().toString();
|
||||
|
||||
Request request = mock(Request.class);
|
||||
when(request.header(anyString(), anyString())).thenReturn(request);
|
||||
ContentResponse response = mock(ContentResponse.class);
|
||||
when(response.getStatus()).thenReturn(200);
|
||||
when(request.send()).thenReturn(response);
|
||||
when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
|
||||
when(response.getContentAsString()).thenReturn(
|
||||
GsonUtils.DEFAULT_GSON_INSTANCE.toJson(List.of(UserDefinedStateTest.createTestState(stateId))));
|
||||
|
||||
List<UserDefinedState> userStates = fixture.getUserStates();
|
||||
assertEquals(1, userStates.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserStatesReturnsEmptyListIfRequestNotSuccessful()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
|
||||
|
||||
Request request = mock(Request.class);
|
||||
when(request.header(anyString(), anyString())).thenReturn(request);
|
||||
ContentResponse response = mock(ContentResponse.class);
|
||||
when(response.getStatus()).thenReturn(401);
|
||||
when(request.send()).thenReturn(response);
|
||||
when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
|
||||
|
||||
List<UserDefinedState> userStates = fixture.getUserStates();
|
||||
assertTrue(userStates.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserStatesReturnsEmptyListIfExceptionHappened()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
|
||||
|
||||
Request request = mock(Request.class);
|
||||
when(request.header(anyString(), anyString())).thenReturn(request);
|
||||
ContentResponse response = mock(ContentResponse.class);
|
||||
when(response.getStatus()).thenReturn(401);
|
||||
when(request.send()).thenThrow(new TimeoutException("text exception"));
|
||||
when(httpClient.createRequest(anyString(), same(HttpMethod.GET))).thenReturn(request);
|
||||
|
||||
List<UserDefinedState> userStates = fixture.getUserStates();
|
||||
assertTrue(userStates.isEmpty());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void afterEach() throws Exception {
|
||||
fixture.dispose();
|
||||
|
@ -50,6 +50,7 @@ import org.openhab.binding.boschshc.internal.devices.bridge.dto.DeviceServiceDat
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.LongPollResult;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Scenario;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.SubscribeResult;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.LongPollingFailedException;
|
||||
|
||||
@ -249,9 +250,8 @@ class LongPollingTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void startLongPolling_receiveScenario()
|
||||
void startLongPollingReceiveScenario()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
// when(httpClient.getBoschSmartHomeUrl(anyString())).thenCallRealMethod();
|
||||
when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
|
||||
|
||||
Request subscribeRequest = mock(Request.class);
|
||||
@ -290,6 +290,47 @@ class LongPollingTest {
|
||||
assertEquals("1693758693032", longPollResultItem.lastTimeTriggered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void startLongPollingReceiveUserDefinedState()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
when(httpClient.getBoschShcUrl(anyString())).thenCallRealMethod();
|
||||
|
||||
Request subscribeRequest = mock(Request.class);
|
||||
when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
|
||||
argThat((JsonRpcRequest r) -> "RE/subscribe".equals(r.method)))).thenReturn(subscribeRequest);
|
||||
SubscribeResult subscribeResult = new SubscribeResult();
|
||||
when(httpClient.sendRequest(any(), same(SubscribeResult.class), any(), any())).thenReturn(subscribeResult);
|
||||
|
||||
Request longPollRequest = mock(Request.class);
|
||||
when(httpClient.createRequest(anyString(), same(HttpMethod.POST),
|
||||
argThat((JsonRpcRequest r) -> "RE/longPoll".equals(r.method)))).thenReturn(longPollRequest);
|
||||
|
||||
fixture.start(httpClient);
|
||||
|
||||
ArgumentCaptor<CompleteListener> completeListener = ArgumentCaptor.forClass(CompleteListener.class);
|
||||
verify(longPollRequest).send(completeListener.capture());
|
||||
|
||||
BufferingResponseListener bufferingResponseListener = (BufferingResponseListener) completeListener.getValue();
|
||||
|
||||
String longPollResultJSON = "{\"result\":[{\"deleted\":false,\"@type\":\"userDefinedState\",\"name\":\"My User state\",\"id\":\"23d34fa6-382a-444d-8aae-89c706e22155\",\"state\":true}],\"jsonrpc\":\"2.0\"}\n";
|
||||
Response response = mock(Response.class);
|
||||
bufferingResponseListener.onContent(response,
|
||||
ByteBuffer.wrap(longPollResultJSON.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
Result result = mock(Result.class);
|
||||
bufferingResponseListener.onComplete(result);
|
||||
|
||||
ArgumentCaptor<LongPollResult> longPollResultCaptor = ArgumentCaptor.forClass(LongPollResult.class);
|
||||
verify(longPollHandler).accept(longPollResultCaptor.capture());
|
||||
LongPollResult longPollResult = longPollResultCaptor.getValue();
|
||||
assertEquals(1, longPollResult.result.size());
|
||||
assertEquals(longPollResult.result.get(0).getClass(), UserDefinedState.class);
|
||||
UserDefinedState longPollResultItem = (UserDefinedState) longPollResult.result.get(0);
|
||||
assertEquals("23d34fa6-382a-444d-8aae-89c706e22155", longPollResultItem.getId());
|
||||
assertEquals("My User state", longPollResultItem.getName());
|
||||
assertTrue(longPollResultItem.isState());
|
||||
}
|
||||
|
||||
@Test
|
||||
void startSubscriptionFailure()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
|
@ -62,7 +62,7 @@ class ScenarioHandlerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void triggerScenario_ShouldSendPOST_ToBoschAPI() throws Exception {
|
||||
void triggerScenarioShouldSendPOSTToBoschAPI() throws Exception {
|
||||
// GIVEN
|
||||
final var httpClient = mock(BoschHttpClient.class);
|
||||
final var request = mock(Request.class);
|
||||
@ -85,7 +85,7 @@ class ScenarioHandlerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void triggerScenario_ShouldNoSendPOST_ToScenarioNameDoesNotExist() throws Exception {
|
||||
void triggerScenarioShouldNoSendPOSTToScenarioNameDoesNotExist() throws Exception {
|
||||
// GIVEN
|
||||
final var httpClient = mock(BoschHttpClient.class);
|
||||
final var request = mock(Request.class);
|
||||
@ -106,7 +106,7 @@ class ScenarioHandlerTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("exceptionData")
|
||||
void triggerScenario_ShouldNotPanic_IfBoschAPIThrowsException(final Exception exception) throws Exception {
|
||||
void triggerScenarioShouldNotPanicIfBoschAPIThrowsException(final Exception exception) throws Exception {
|
||||
// GIVEN
|
||||
final var httpClient = mock(BoschHttpClient.class);
|
||||
final var request = mock(Request.class);
|
||||
@ -126,7 +126,7 @@ class ScenarioHandlerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void triggerScenario_ShouldNotPanic_IfPOSTIsNotSuccessful() throws Exception {
|
||||
void triggerScenarioShouldNotPanicIfPOSTIsNotSuccessful() throws Exception {
|
||||
// GIVEN
|
||||
final var httpClient = mock(BoschHttpClient.class);
|
||||
final var request = mock(Request.class);
|
||||
@ -150,7 +150,7 @@ class ScenarioHandlerTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("httpExceptionData")
|
||||
void triggerScenario_ShouldNotPanic_IfPOSTThrowsException(final Exception exception) throws Exception {
|
||||
void triggerScenarioShouldNotPanicIfPOSTThrowsException(final Exception exception) throws Exception {
|
||||
// GIVEN
|
||||
final var httpClient = mock(BoschHttpClient.class);
|
||||
final var request = mock(Request.class);
|
||||
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.devices.bridge.dto;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for UserDefinedStateTest
|
||||
*
|
||||
* @author Patrick Gell - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class UserDefinedStateTest {
|
||||
|
||||
public static UserDefinedState createTestState(final String id) {
|
||||
UserDefinedState userState = new UserDefinedState();
|
||||
userState.setId(id);
|
||||
userState.setState(true);
|
||||
userState.setName("test user state");
|
||||
return userState;
|
||||
}
|
||||
|
||||
private @NonNullByDefault({}) UserDefinedState fixture;
|
||||
private final String testId = UUID.randomUUID().toString();
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
fixture = createTestState(testId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsValid() {
|
||||
assertTrue(UserDefinedState.isValid(fixture));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
assertEquals(
|
||||
String.format("UserDefinedState{id='%s', name='test user state', state=true, type='UserDefinedState'}",
|
||||
testId),
|
||||
fixture.toString());
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.devices.userdefinedstate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.openhab.binding.boschshc.internal.devices.AbstractBoschSHCHandlerTest;
|
||||
import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
|
||||
import org.openhab.binding.boschshc.internal.exceptions.BoschSHCException;
|
||||
import org.openhab.binding.boschshc.internal.services.userstate.dto.UserStateServiceState;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
|
||||
/**
|
||||
* Unit tests for UserStateHandlerTest
|
||||
*
|
||||
* @author Patrick Gell - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class UserStateHandlerTest extends AbstractBoschSHCHandlerTest<UserStateHandler> {
|
||||
|
||||
private final Configuration config = new Configuration(Map.of("id", UUID.randomUUID().toString()));
|
||||
|
||||
@Override
|
||||
protected UserStateHandler createFixture() {
|
||||
return new UserStateHandler(getThing());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingTypeUID getThingTypeUID() {
|
||||
return BoschSHCBindingConstants.THING_TYPE_USER_DEFINED_STATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Configuration getConfiguration() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHandleCommandSetState()
|
||||
throws InterruptedException, TimeoutException, ExecutionException, BoschSHCException {
|
||||
var channel = new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_USER_DEFINED_STATE);
|
||||
getFixture().handleCommand(channel, OnOffType.ON);
|
||||
|
||||
ArgumentCaptor<String> deviceId = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<UserStateServiceState> stateClass = ArgumentCaptor.forClass(UserStateServiceState.class);
|
||||
|
||||
verify(getBridgeHandler()).getUserStateInfo(config.get("id").toString());
|
||||
verify(getBridgeHandler()).getState(anyString(), anyString(), any());
|
||||
verify(getBridgeHandler()).putState(deviceId.capture(), anyString(), stateClass.capture());
|
||||
|
||||
assertNotNull(deviceId.getValue());
|
||||
assertEquals(channel.getThingUID().getId(), deviceId.getValue());
|
||||
|
||||
assertNotNull(stateClass.getValue());
|
||||
assertTrue(stateClass.getValue().isState());
|
||||
}
|
||||
|
||||
@ParameterizedTest()
|
||||
@MethodSource("provideExceptions")
|
||||
void testHandleCommandSetStateUpdatesThingStatusOnException(Exception mockException)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
reset(getCallback());
|
||||
lenient().when(getBridgeHandler().putState(anyString(), anyString(), any(UserStateServiceState.class)))
|
||||
.thenThrow(mockException);
|
||||
var channel = new ChannelUID(getThing().getUID(), BoschSHCBindingConstants.CHANNEL_USER_DEFINED_STATE);
|
||||
getFixture().handleCommand(channel, OnOffType.ON);
|
||||
|
||||
verify(getCallback()).getBridge(any(ThingUID.class));
|
||||
|
||||
ThingStatusInfo expectedStatusInfo = new ThingStatusInfo(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
String.format("Error while putting user-defined state for %s", channel.getThingUID().getId()));
|
||||
verify(getCallback()).statusUpdated(same(getThing()), eq(expectedStatusInfo));
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideExceptions() {
|
||||
return Stream.of(Arguments.of(new TimeoutException("test exception")),
|
||||
Arguments.of(new InterruptedException("test exception")));
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@ -32,6 +33,7 @@ import org.openhab.binding.boschshc.internal.devices.BoschSHCBindingConstants;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.BridgeHandler;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Device;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.Room;
|
||||
import org.openhab.binding.boschshc.internal.devices.bridge.dto.UserDefinedState;
|
||||
import org.openhab.core.config.discovery.DiscoveryListener;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
@ -234,4 +236,29 @@ class ThingDiscoveryServiceTest {
|
||||
device.deviceModel = "TWINGUARD";
|
||||
assertThat(fixture.getThingTypeUID(device), is(BoschSHCBindingConstants.THING_TYPE_TWINGUARD));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddUserDefinedStates() {
|
||||
mockBridgeCalls();
|
||||
|
||||
ArrayList<UserDefinedState> userStates = new ArrayList<>();
|
||||
|
||||
UserDefinedState userState1 = new UserDefinedState();
|
||||
userState1.setId(UUID.randomUUID().toString());
|
||||
userState1.setName("first defined state");
|
||||
userState1.setState(true);
|
||||
UserDefinedState userState2 = new UserDefinedState();
|
||||
userState2.setId(UUID.randomUUID().toString());
|
||||
userState2.setName("another defined state");
|
||||
userState2.setState(false);
|
||||
userStates.add(userState1);
|
||||
userStates.add(userState2);
|
||||
|
||||
verify(discoveryListener, never()).thingDiscovered(any(), any());
|
||||
|
||||
fixture.addUserStates(userStates);
|
||||
|
||||
// two calls for the two devices expected
|
||||
verify(discoveryListener, times(2)).thingDiscovered(any(), any());
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,10 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.boschshc.internal.serialization.GsonUtils;
|
||||
import org.openhab.binding.boschshc.internal.services.userstate.dto.UserStateServiceState;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
* Test class
|
||||
@ -79,4 +81,12 @@ class BoschSHCServiceStateTest {
|
||||
TestState2.class);
|
||||
assertNotEquals(null, state2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromJsonReturnsUserStateServiceStateForValidJson() {
|
||||
var state = BoschSHCServiceState.fromJson(new JsonPrimitive("false"), UserStateServiceState.class);
|
||||
assertNotEquals(null, state);
|
||||
assertTrue(state.getClass().isAssignableFrom(UserStateServiceState.class));
|
||||
assertFalse(state.isState());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.userstate.dto;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
|
||||
/**
|
||||
* Unit tests for UserStateServiceStateTest
|
||||
*
|
||||
* @author Patrick Gell - Initial contribution
|
||||
*/
|
||||
class UserStateServiceStateTest {
|
||||
|
||||
UserStateServiceState subject;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
subject = new UserStateServiceState();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideStringsForIsBlank")
|
||||
void setStateFromStringUpdatesTheState(String inputState, boolean expectedState) {
|
||||
subject.setStateFromString(inputState);
|
||||
|
||||
assertEquals(expectedState, subject.isState());
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideStringsForIsBlank() {
|
||||
return Stream.of(Arguments.of("true", true), Arguments.of("false", false), Arguments.of("True", true),
|
||||
Arguments.of("False", false), Arguments.of("TRUE", true), Arguments.of("FALSE", false),
|
||||
Arguments.of(null, false), Arguments.of("", false), Arguments.of(" ", false),
|
||||
Arguments.of("not blank", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStateAsStringReturnsState() {
|
||||
subject.setState(false);
|
||||
|
||||
assertEquals("false", subject.getStateAsString());
|
||||
|
||||
subject.setState(true);
|
||||
assertEquals("true", subject.getStateAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toOnOffTypeReturnsType() {
|
||||
subject.setState(false);
|
||||
|
||||
assertEquals(OnOffType.OFF, subject.toOnOffType());
|
||||
|
||||
subject.setState(true);
|
||||
assertEquals(OnOffType.ON, subject.toOnOffType());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user