[dirigera] Bugfix startup synchronization (#20184)

* remove unknown status

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
This commit is contained in:
Bernd Weymann
2026-03-08 10:16:40 +01:00
committed by GitHub
parent d805b71df2
commit e9b8e6a27d
45 changed files with 529 additions and 541 deletions
@@ -21,6 +21,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -44,6 +46,7 @@ import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.BridgeHandler;
@@ -66,17 +69,20 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public class BaseHandler extends BaseThingHandler implements DebugHandler {
private static final ThingStatusInfo UNKNOWN_INITIALZING = new ThingStatusInfo(ThingStatus.UNKNOWN,
ThingStatusDetail.NONE, null);
private static final ThingStatusInfo UNKNOWN_NOT_READY = new ThingStatusInfo(ThingStatus.UNKNOWN,
ThingStatusDetail.NOT_YET_READY, null);
private final Logger logger = LoggerFactory.getLogger(BaseHandler.class);
private List<PowerListener> powerListeners = new ArrayList<>();
private @Nullable ScheduledFuture<?> initializationFuture;
private @Nullable Gateway gateway;
// to be overwritten by child class in order to route the updates to the right instance
protected @Nullable BaseHandler child;
protected BaseHandler child;
// maps to route properties to channels and vice versa
protected Map<String, String> property2ChannelMap;
protected Map<String, String> channel2PropertyMap;
// cache to handle each refresh command properly
protected Map<String, State> channelStateMap;
@@ -111,6 +117,7 @@ public class BaseHandler extends BaseThingHandler implements DebugHandler {
property2ChannelMap = mapping;
channel2PropertyMap = reverse(mapping);
channelStateMap = initializeCache(mapping);
this.child = this;
}
protected void setChildHandler(BaseHandler child) {
@@ -125,11 +132,11 @@ public class BaseHandler extends BaseThingHandler implements DebugHandler {
// first get bridge as Gateway
Bridge bridge = getBridge();
if (bridge != null) {
updateStatus(ThingStatus.UNKNOWN);
BridgeHandler handler = bridge.getHandler();
if (handler != null) {
if (handler instanceof Gateway gw) {
gateway = gw;
checkBridge();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/dirigera.device.status.wrong-bridge-type");
@@ -145,22 +152,66 @@ public class BaseHandler extends BaseThingHandler implements DebugHandler {
"@text/dirigera.device.status.missing-bridge");
return;
}
if (!checkHandler()) {
// if handler doesn't match model status will be set to offline and it will stay until correction
if (config.id.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/dirigera.device.status.id-mandatory");
return;
}
if (!config.id.isBlank()) {
updateProperties();
BaseHandler proxy = child;
if (proxy != null) {
gateway().registerDevice(proxy, config.id);
}
}
}
private void updateProperties() {
public synchronized void checkBridge() {
// disposed flag set - go away
if (disposed) {
return;
}
/*
* UNKNOWN => device initialization in progress, do nothing and wait for it to finish with resulting
* ONLINE(OFFLINE response
* CONFIGURATION_ERROR => something went wrong during initialize, don't continue
*/
ThingStatusInfo handlerStatusInfo = getThing().getStatusInfo();
if (UNKNOWN_INITIALZING.equals(handlerStatusInfo)
|| ThingStatusDetail.CONFIGURATION_ERROR.equals(handlerStatusInfo.getStatusDetail())) {
return;
}
// Bridge ONLINE, thing anything else than ONLINE => initialize device and wait for it to finish with resulting
// ONLINE(OFFLINE response, if
if (ThingStatus.ONLINE.equals(gateway().getThing().getStatus())
&& !ThingStatus.ONLINE.equals(handlerStatusInfo.getStatus())) {
updateStatus(UNKNOWN_INITIALZING.getStatus(), UNKNOWN_INITIALZING.getStatusDetail(), null);
initializationFuture = scheduler.schedule(child::initializeDevice, 0, TimeUnit.SECONDS);
} else {
updateStatus(UNKNOWN_NOT_READY.getStatus(), UNKNOWN_NOT_READY.getStatusDetail(), null);
}
}
/**
* Check Handler sanity
* Initial channel update
* Thing properties
* Register device at gateway
*/
public void initializeDevice() {
if (!checkHandler()) {
// if handler doesn't match model status will be set to offline and it will stay until correction
return;
} else {
updateProperties();
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
gateway().registerDevice(child, config.id);
}
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
super.bridgeStatusChanged(bridgeStatusInfo);
checkBridge();
}
protected void updateProperties() {
// fill canSend and canReceive capabilities
Map<String, Object> modelProperties = gateway().model().getPropertiesFor(config.id);
Object canReceiveCapabilities = modelProperties.get(Model.PROPERTY_CAN_RECEIVE);
@@ -318,17 +369,6 @@ public class BaseHandler extends BaseThingHandler implements DebugHandler {
if (customDebug) {
logger.info("DIRIGERA BASE_HANDLER {} handleUpdate JSON {}", thing.getUID(), update);
}
// check online offline for each device
if (update.has(Model.REACHABLE)) {
if (update.getBoolean(Model.REACHABLE)) {
updateStatus(ThingStatus.ONLINE);
online = true;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/dirigera.device.status.not-reachable");
online = false;
}
}
if (update.has(PROPERTY_DEVICE_TYPE) && deviceType.isBlank()) {
deviceType = update.getString(PROPERTY_DEVICE_TYPE);
}
@@ -405,13 +445,25 @@ public class BaseHandler extends BaseThingHandler implements DebugHandler {
updateList.add(link.toString());
});
Collections.sort(updateList);
Collections.sort(hardLinks);
if (!hardLinks.equals(updateList)) {
hardLinks = updateList;
List<String> localHardLinks = getLinks();
Collections.sort(localHardLinks);
if (!localHardLinks.equals(updateList)) {
setLinks(updateList);
// just update internal link list and let the gateway update do all updates regarding soft links
gateway().updateLinks();
}
}
// check online offline for each device
if (update.has(Model.REACHABLE)) {
if (update.getBoolean(Model.REACHABLE)) {
updateStatus(ThingStatus.ONLINE);
online = true;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/dirigera.device.status.not-reachable");
online = false;
}
}
}
protected synchronized void createChannelIfNecessary(String channelId, String channelTypeUID, String itemType) {
@@ -450,19 +502,24 @@ public class BaseHandler extends BaseThingHandler implements DebugHandler {
@Override
public void dispose() {
disposed = true;
ScheduledFuture<?> localFuture = initializationFuture;
if (localFuture != null) {
localFuture.cancel(true);
initializationFuture = null;
}
online = false;
BaseHandler proxy = child;
if (proxy != null) {
gateway().unregisterDevice(proxy, config.id);
Gateway localGateway = gateway;
if (localGateway != null) {
localGateway.unregisterDevice(child, config.id);
}
super.dispose();
}
@Override
public void handleRemoval() {
BaseHandler proxy = child;
if (proxy != null) {
gateway().deleteDevice(proxy, config.id);
Gateway localGateway = gateway;
if (localGateway != null) {
localGateway.deleteDevice(child, config.id);
}
super.handleRemoval();
}
@@ -487,7 +544,6 @@ public class BaseHandler extends BaseThingHandler implements DebugHandler {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
"@text/dirigera.device.status.id-not-found" + " [\"" + config.id + "\"]");
} else {
// String message = "Handler " + thing.getThingTypeUID() + " doesn't match with model " + modelTTUID;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/dirigera.device.status.ttuid-mismatch" + " [\"" + thing.getThingTypeUID() + "\",\""
+ modelTTUID + "\"]");
@@ -530,10 +586,14 @@ public class BaseHandler extends BaseThingHandler implements DebugHandler {
*
* @return links attached to this device
*/
public List<String> getLinks() {
public synchronized List<String> getLinks() {
return new ArrayList<String>(hardLinks);
}
private synchronized void setLinks(List<String> newLinks) {
hardLinks = new ArrayList<>(newLinks);
}
private void linkUpdate(String linkedDeviceId, boolean add) {
/**
* link has to be set to target device like light or outlet, not to the device which triggers an action like
@@ -14,7 +14,6 @@ package org.openhab.binding.dirigera.internal.handler;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -94,7 +93,6 @@ public class DirigeraHandler extends BaseBridgeHandler implements Gateway, Debug
// Can be overwritten by Unit test for mocking API
protected Map<String, State> channelStateMap = new HashMap<>();
protected Class<?> apiProvider = DirigeraAPIImpl.class;
private final Map<String, BaseHandler> deviceTree = new HashMap<>();
private final DirigeraDiscoveryService discoveryService;
@@ -146,6 +144,8 @@ public class DirigeraHandler extends BaseBridgeHandler implements Gateway, Debug
locationOptions.add(new CommandOption(location.toFullString(), "Home location"));
}
commandProvider.setCommandOptions(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), locationOptions);
api = Optional.of(new DirigeraAPIImpl(httpClient, this));
}
@Override
@@ -249,25 +249,7 @@ public class DirigeraHandler extends BaseBridgeHandler implements Gateway, Debug
// Step 2 - if token is fine start initializing, else set status pairing retry
if (!token.isBlank()) {
// now create api and model
try {
DirigeraAPI apiProviderInstance = (DirigeraAPI) apiProvider
.getConstructor(HttpClient.class, Gateway.class).newInstance(httpClient, this);
if (apiProviderInstance != null) {
api = Optional.of(apiProviderInstance);
} else {
throw (new InstantiationException(apiProvider.descriptorString()));
}
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
// this will not happen - DirirgeraAPIIMpl tested with mocks in unit tests
logger.error("Error {}", apiProvider.descriptorString());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
"@text/dirigera.gateway.status.api-error" + " [\"" + apiProvider.descriptorString() + "\"]");
return;
}
Model houseModel = new DirigeraModel(this);
model = Optional.of(houseModel);
model = Optional.of(new DirigeraModel(this));
modelUpdate(); // initialize model
websocket.initialize();
// checks API access and starts websocket
@@ -284,6 +266,10 @@ public class DirigeraHandler extends BaseBridgeHandler implements Gateway, Debug
}
}
void setAPIHandler(DirigeraAPI api) {
this.api = Optional.of(api);
}
@Override
public void dispose() {
super.dispose();
@@ -57,15 +57,6 @@ public class AirPurifierHandler extends BaseHandler {
super.setChildHandler(this);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -49,15 +49,6 @@ public class BlindHandler extends BaseHandler {
linkCandidateTypes = List.of(DEVICE_TYPE_BLIND_CONTROLLER);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String channel = channelUID.getIdWithoutGroup();
@@ -86,10 +86,7 @@ public class BaseShortcutController extends BaseHandler {
@Override
public void dispose() {
sceneMapping.forEach((key, value) -> {
BaseHandler proxy = child;
if (proxy != null) {
gateway().unregisterDevice(proxy, value);
}
gateway().unregisterDevice(child, value);
});
super.dispose();
}
@@ -98,10 +95,7 @@ public class BaseShortcutController extends BaseHandler {
public void handleRemoval() {
sceneMapping.forEach((key, value) -> {
// cleanup storage and hub
BaseHandler proxy = child;
if (proxy != null) {
gateway().deleteDevice(proxy, value);
}
gateway().deleteDevice(child, value);
gateway().api().deleteScene(value);
storage.remove(key);
});
@@ -39,15 +39,6 @@ public class BlindsControllerHandler extends BaseHandler {
linkCandidateTypes = List.of(DEVICE_TYPE_BLINDS);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -38,28 +38,27 @@ public class DoubleShortcutControllerHandler extends BaseShortcutController {
}
@Override
public void initialize() {
super.initialize();
public void initializeDevice() {
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
super.updateProperties();
// now register at gateway all device and scene ids
String relationId = gateway().model().getRelationId(config.id);
relations = gateway().model().getRelations(relationId);
Entry<String, String> firstEntry = relations.firstEntry();
String firstDeviceId = firstEntry.getKey();
super.initializeScenes(firstDeviceId, CHANNEL_BUTTON_1);
gateway().registerDevice(this, firstDeviceId);
values = gateway().api().readDevice(firstDeviceId);
handleUpdate(values);
// double shortcut controller has 2 devices
Entry<String, String> secondEntry = relations.higherEntry(firstEntry.getKey());
String secondDeviceId = secondEntry.getKey();
super.initializeScenes(secondDeviceId, CHANNEL_BUTTON_2);
gateway().registerDevice(this, secondDeviceId);
JSONObject values = gateway().api().readDevice(firstDeviceId);
handleUpdate(values);
gateway().registerDevice(this, firstDeviceId);
values = gateway().api().readDevice(secondDeviceId);
handleUpdate(values);
gateway().registerDevice(this, secondDeviceId);
}
}
@@ -43,15 +43,6 @@ public class LightControllerHandler extends BaseHandler {
linkCandidateTypes = List.of(DEVICE_TYPE_LIGHT, DEVICE_TYPE_OUTLET);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -17,7 +17,6 @@ import static org.openhab.binding.dirigera.internal.Constants.CHANNEL_BUTTON_1;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.json.JSONObject;
import org.openhab.core.storage.Storage;
import org.openhab.core.thing.Thing;
@@ -35,12 +34,10 @@ public class ShortcutControllerHandler extends BaseShortcutController {
}
@Override
public void initialize() {
super.initialize();
public void initializeDevice() {
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
super.initializeScenes(config.id, CHANNEL_BUTTON_1);
}
super.initializeDevice();
}
}
@@ -18,7 +18,6 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.json.JSONObject;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.core.thing.Thing;
@@ -36,13 +35,4 @@ public class SoundControllerHandler extends BaseHandler {
// links of types which can be established towards this device
linkCandidateTypes = List.of(DEVICE_TYPE_SPEAKER);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
}
@@ -52,15 +52,6 @@ public class ColorLightHandler extends TemperatureLightHandler {
super.setChildHandler(this);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void dispose() {
super.dispose();
@@ -40,15 +40,6 @@ public class DimmableLightHandler extends BaseLight {
super.setChildHandler(this);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -15,7 +15,6 @@ package org.openhab.binding.dirigera.internal.handler.light;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.json.JSONObject;
import org.openhab.core.thing.Thing;
/**
@@ -30,13 +29,4 @@ public class SwitchLightHandler extends BaseLight {
super(thing, mapping);
super.setChildHandler(this);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
}
@@ -56,8 +56,7 @@ public class TemperatureLightHandler extends DimmableLightHandler {
}
@Override
public void initialize() {
super.initialize();
public void initializeDevice() {
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
JSONObject attributes = values.getJSONObject(Model.ATTRIBUTES);
@@ -74,6 +73,18 @@ public class TemperatureLightHandler extends DimmableLightHandler {
properties.put("colorTemperatureMax", String.valueOf(colorTemperatureMax));
}
}
// KAJPLATS is delivering color temperature in Mired https://en.wikipedia.org/wiki/Mired
if (colorTemperatureMin - colorTemperatureMax < 0) {
properties.put("colorTemperatureMin", String.valueOf(colorTemperatureMin) + "M");
properties.put("colorTemperatureMax", String.valueOf(colorTemperatureMax) + "M");
colorTemperatureMin = 1000000 / colorTemperatureMin;
colorTemperatureMax = 1000000 / colorTemperatureMax;
} else {
properties.put("colorTemperatureMin", String.valueOf(colorTemperatureMin) + "K");
properties.put("colorTemperatureMax", String.valueOf(colorTemperatureMax) + "K");
}
StateDescriptionFragment fragment = StateDescriptionFragmentBuilder.create()
.withMinimum(BigDecimal.valueOf(colorTemperatureMax))
.withMaximum(BigDecimal.valueOf(colorTemperatureMin)).withStep(BigDecimal.valueOf(100))
@@ -81,8 +92,8 @@ public class TemperatureLightHandler extends DimmableLightHandler {
stateProvider.setStateDescription(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE_ABS), fragment);
updateProperties(properties);
range = colorTemperatureMin - colorTemperatureMax;
handleUpdate(values);
}
super.initializeDevice();
}
@Override
@@ -37,12 +37,6 @@ public class PowerPlugHandler extends SimplePlugHandler {
super.setChildHandler(this);
}
@Override
public void initialize() {
super.initialize();
// update of values is handled in super class
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -18,7 +18,6 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.json.JSONObject;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.core.thing.Thing;
@@ -35,14 +34,4 @@ public class SimplePlugHandler extends BaseHandler {
// links of types which can be established towards this device
linkCandidateTypes = List.of(DEVICE_TYPE_LIGHT_CONTROLLER, DEVICE_TYPE_MOTION_SENSOR);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
// handling of first update, also for PowerPlug and SmartPlug child classes!
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
}
@@ -44,12 +44,6 @@ public class SmartPlugHandler extends PowerPlugHandler {
super.setChildHandler(this);
}
@Override
public void initialize() {
super.initialize();
// update of values is handled in super class
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -34,15 +34,6 @@ public class RepeaterHandler extends BaseHandler {
super.setChildHandler(this);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -50,8 +50,8 @@ public class SceneHandler extends BaseHandler {
}
@Override
public void initialize() {
super.initialize();
public void initializeDevice() {
super.initializeDevice();
if (super.checkHandler()) {
JSONObject values = gateway().api().readScene(config.id);
handleUpdate(values);
@@ -62,6 +62,7 @@ public class SceneHandler extends BaseHandler {
} else {
updateStatus(ThingStatus.ONLINE);
}
gateway().registerDevice(this, config.id);
// check if different undo duration is configured
if (values.has("undoAllowedDuration")) {
@@ -45,15 +45,6 @@ public class AirQualityHandler extends BaseHandler {
hardLinks = Arrays.asList();
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -42,15 +42,6 @@ public class ContactSensorHandler extends BaseHandler {
hardLinks = Arrays.asList();
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -39,15 +39,6 @@ public class LightSensorHandler extends BaseHandler {
super.setChildHandler(this);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -12,7 +12,7 @@
*/
package org.openhab.binding.dirigera.internal.handler.sensor;
import static org.openhab.binding.dirigera.internal.Constants.*;
import static org.openhab.binding.dirigera.internal.Constants.CHANNEL_ILLUMINANCE;
import java.util.Iterator;
import java.util.Map;
@@ -42,21 +42,17 @@ public class MotionLightSensorHandler extends MotionSensorHandler {
}
@Override
public void initialize() {
super.initialize();
public void initializeDevice() {
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
// assure deviceType is set from main device
if (values.has(PROPERTY_DEVICE_TYPE)) {
deviceType = values.getString(PROPERTY_DEVICE_TYPE);
}
updateProperties();
// get all relations and register
String relationId = gateway().model().getRelationId(config.id);
relations = gateway().model().getRelations(relationId);
// register for updates of twin devices
relations.forEach((key, value) -> {
// assure deviceType is set from main device
if (config.id.equals(key)) {
deviceType = value;
}
gateway().registerDevice(this, key);
JSONObject relationValues = gateway().api().readDevice(key);
handleUpdate(relationValues);
@@ -59,15 +59,6 @@ public class MotionSensorHandler extends BaseHandler {
linkCandidateTypes = List.of(DEVICE_TYPE_LIGHT, DEVICE_TYPE_OUTLET);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -42,15 +42,6 @@ public class WaterSensorHandler extends BaseHandler {
hardLinks = Arrays.asList();
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
super.handleCommand(channelUID, command);
@@ -50,15 +50,6 @@ public class SpeakerHandler extends BaseHandler {
linkCandidateTypes = List.of(DEVICE_TYPE_SOUND_CONTROLLER);
}
@Override
public void initialize() {
super.initialize();
if (super.checkHandler()) {
JSONObject values = gateway().api().readDevice(config.id);
handleUpdate(values);
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
String channel = channelUID.getIdWithoutGroup();
@@ -379,6 +379,7 @@ dirigera.device.status.missing-bridge = No Bridge configured
dirigera.device.status.not-reachable = Device not reachable
dirigera.device.status.api-error = API {0} cannot be created
dirigera.device.status.id-not-found = Device id {0} not found
dirigera.device.status.id-mandatory = Device id is mandatory configuration
dirigera.device.status.ttuid-mismatch = Handler {0} doesn't match with model {1}
dirigera.gateway.status.pairing-button = Press Button on DIRIGERA Gateway
dirigera.gateway.status.pairing-retry = Pairing failed. Stop and start bridge to initialize new pairing.
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="dirigera"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="light-sensor">
<supported-bridge-type-refs>
<bridge-type-ref id="gateway"/>
</supported-bridge-type-refs>
<label>Light Sensor</label>
<description>Sensor measuring illuminance in your room</description>
<channels>
<channel id="illuminance" typeId="illuminance"/>
</channels>
<config-description-ref uri="thing-type:dirigera:base-device"/>
</thing-type>
</thing:thing-descriptions>
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2010-2026 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.dirigera.internal;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.handler.sensor.MotionSensorHandler;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.internal.ThingImpl;
/**
* {@link TestBridgeStatus} Tests different bridge modes for device initialization
*
* @author Bernd Weymann - Initial Contribution
*/
@NonNullByDefault
class TestBridgeStatus {
private static final ThingStatusInfo INITIALIZING = new ThingStatusInfo(ThingStatus.INITIALIZING,
ThingStatusDetail.NONE, null);
private static final ThingStatusInfo UNKNOWN_NONE = new ThingStatusInfo(ThingStatus.UNKNOWN, ThingStatusDetail.NONE,
null);
private static final ThingStatusInfo UNKNOWN_NOT_READY = new ThingStatusInfo(ThingStatus.UNKNOWN,
ThingStatusDetail.NOT_YET_READY, null);
private static final ThingStatusInfo ONLINE = new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
private static final ThingStatusInfo OFFLINE_CONFIG_ERROR = new ThingStatusInfo(ThingStatus.OFFLINE,
ThingStatusDetail.CONFIGURATION_ERROR, null);
private static Stream<Arguments> testBridgeStatusChanges() {
return Stream.of(//
Arguments.of(UNKNOWN_NONE, INITIALIZING, UNKNOWN_NOT_READY), // Bridge not ready yet
Arguments.of(ONLINE, OFFLINE_CONFIG_ERROR, OFFLINE_CONFIG_ERROR), // Stay at config error, no init shall
// happen
Arguments.of(ONLINE, INITIALIZING, ONLINE) // Go from init straight to ONLINE
);
}
@ParameterizedTest
@MethodSource
void testBridgeStatusChanges(ThingStatusInfo bridgeStatusInfo, ThingStatusInfo handlerStatusInfo,
ThingStatusInfo expectedStatusInfo) {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge();
hubBridge.setStatusInfo(bridgeStatusInfo);
ThingImpl thing = new ThingImpl(THING_TYPE_MOTION_SENSOR, "test-device");
thing.setBridgeUID(hubBridge.getBridgeUID());
MotionSensorHandler handler = new MotionSensorHandler(thing, MOTION_SENSOR_MAP);
CallbackMock thingCallback = new CallbackMock();
thingCallback.setBridge(hubBridge);
handler.setCallback(thingCallback);
// set the right id
Map<String, Object> config = new HashMap<>();
config.put("id", "ee61c57f-8efa-44f4-ba8a-d108ae054138_1");
handler.handleConfigurationUpdate(config);
hubBridge.setStatusInfo(bridgeStatusInfo);
thingCallback.statusUpdated(thing, handlerStatusInfo);
handler.initialize();
thingCallback.waitForStatus(expectedStatusInfo.getStatus());
assertEquals(expectedStatusInfo.getStatus(), thingCallback.getStatus().getStatus(), "Status Check");
assertEquals(expectedStatusInfo.getStatusDetail(), thingCallback.getStatus().getStatusDetail(),
"Status Detail Check");
}
}
@@ -28,7 +28,6 @@ import org.openhab.binding.dirigera.internal.Constants;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.DicoveryServiceMock;
import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu;
import org.openhab.binding.dirigera.internal.mock.DirigeraHandlerManipulator;
import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock;
import org.openhab.core.storage.Storage;
import org.openhab.core.test.storage.VolatileStorageService;
@@ -58,7 +57,7 @@ public class DirigeraBridgeProvider {
public static Bridge prepareSimuBridge(String homeFile, boolean discovery, List<String> knownDevicesd) {
String ipAddress = "1.2.3.4";
HttpClient httpMock = mock(HttpClient.class);
DirigeraAPISimu.fileName = homeFile;
/**
* Prepare persistence
*/
@@ -82,6 +81,8 @@ public class DirigeraBridgeProvider {
*/
DirigeraHandlerManipulator hubHandler = new DirigeraHandlerManipulator(hubBridge, httpMock, mockStorage,
new DicoveryServiceMock());
DirigeraAPISimu apiSimu = new DirigeraAPISimu(httpMock, hubHandler, homeFile);
hubHandler.setAPIHandler(apiSimu);
hubBridge.setHandler(hubHandler);
CallbackMock bridgeCallback = new CallbackMock();
hubHandler.setCallback(bridgeCallback);
@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dirigera.internal.mock;
package org.openhab.binding.dirigera.internal.handler;
import static org.mockito.Mockito.mock;
@@ -19,7 +19,7 @@ import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.dirigera.internal.DirigeraCommandProvider;
import org.openhab.binding.dirigera.internal.discovery.DirigeraDiscoveryService;
import org.openhab.binding.dirigera.internal.handler.DirigeraHandler;
import org.openhab.binding.dirigera.internal.interfaces.DirigeraAPI;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.storage.Storage;
import org.openhab.core.thing.Bridge;
@@ -38,8 +38,6 @@ public class DirigeraHandlerManipulator extends DirigeraHandler {
DirigeraDiscoveryService discoveryService) {
super(bridge, insecureClient, bindingStorage, discoveryService, mock(LocationProvider.class),
mock(DirigeraCommandProvider.class), mock(BundleContext.class));
// Changes the class of the provider. During initialize this class will be used for instantiation
super.apiProvider = DirigeraAPISimu.class;
}
/**
@@ -49,4 +47,9 @@ public class DirigeraHandlerManipulator extends DirigeraHandler {
public @Nullable ThingHandlerCallback getCallback() {
return super.getCallback();
}
@Override
public void setAPIHandler(DirigeraAPI api) {
super.setAPIHandler(api);
}
}
@@ -13,7 +13,6 @@
package org.openhab.binding.dirigera.internal.handler;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.CHANNEL_LOCATION;
import java.util.List;
@@ -22,7 +21,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu;
import org.openhab.binding.dirigera.internal.mock.DirigeraHandlerManipulator;
import org.openhab.core.library.types.PointType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
@@ -40,34 +38,27 @@ import org.openhab.core.types.UnDefType;
*/
@NonNullByDefault
class TestGateway {
private static String deviceId = "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1";
private String deviceId = "594197c3-23c9-4dc7-a6ca-1fe6a8455d29_1";
private static DirigeraHandler handler = mock(DirigeraHandler.class);
private static CallbackMock callback = mock(CallbackMock.class);
private static Thing thing = mock(Thing.class);
private static String mockFile = "src/test/resources/gateway/home-with-coordinates.json";
@Test
void testBridgeCreation() {
DirigeraHandlerManipulator getBridgeHandler(String mockFile) {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge(mockFile, false, List.of());
ThingHandler bridgeHandler = hubBridge.getHandler();
assertTrue(bridgeHandler instanceof DirigeraHandlerManipulator);
handler = (DirigeraHandlerManipulator) bridgeHandler;
thing = handler.getThing();
DirigeraHandler handler = (DirigeraHandlerManipulator) bridgeHandler;
ThingHandlerCallback proxyCallback = ((DirigeraHandlerManipulator) handler).getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
callback = (CallbackMock) proxyCallback;
CallbackMock callback = (CallbackMock) proxyCallback;
handler.initialize();
callback.waitForOnline();
return (DirigeraHandlerManipulator) handler;
}
@Test
void testWithCoordinates() {
mockFile = "src/test/resources/gateway/home-with-coordinates.json";
testBridgeCreation();
DirigeraHandlerManipulator handler = getBridgeHandler("src/test/resources/gateway/home-with-coordinates.json");
assertNotNull(handler);
assertNotNull(thing);
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(callback);
State locationPoint = callback.getState("dirigera:gateway:9876:location");
@@ -78,10 +69,12 @@ class TestGateway {
@Test
void testWithoutCoordinates() {
mockFile = "src/test/resources/gateway/home-without-coordinates.json";
testBridgeCreation();
Bridge hubBridge = DirigeraBridgeProvider
.prepareSimuBridge("src/test/resources/gateway/home-without-coordinates.json", false, List.of());
DirigeraHandlerManipulator handler = (DirigeraHandlerManipulator) hubBridge.getHandler();
assertNotNull(handler);
assertNotNull(thing);
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(callback);
State locationPoint = callback.getState("dirigera:gateway:9876:location");
@@ -91,27 +84,27 @@ class TestGateway {
@Test
void testCommands() {
testWithCoordinates();
DirigeraHandlerManipulator handler = getBridgeHandler("src/test/resources/gateway/home-with-coordinates.json");
assertNotNull(handler);
Thing thing = handler.getThing();
assertNotNull(thing);
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(callback);
DirigeraAPISimu api = (DirigeraAPISimu) handler.api();
// remove location from gateway with empty string
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), StringType.EMPTY);
String patch = DirigeraAPISimu.patchMap.get(deviceId);
String patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"coordinates\":{}}}", patch, "Empty Coordinates");
DirigeraAPISimu.patchMap.clear();
api.clear();
// set new location with valid coordinates
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), StringType.valueOf("9.123,1.987"));
patch = DirigeraAPISimu.patchMap.get(deviceId);
patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"coordinates\":{\"latitude\":9.123,\"longitude\":1.987}}}", patch,
"Valid Coordinates");
DirigeraAPISimu.patchMap.clear();
// nothing send if value is invalid
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_LOCATION), StringType.valueOf("wrong coding"));
patch = DirigeraAPISimu.patchMap.get(deviceId);
assertNull(patch, "Wrong coordinates");
DirigeraAPISimu.patchMap.clear();
api.clear();
}
}
@@ -51,6 +51,7 @@ class TestWrongHandler {
handler.handleConfigurationUpdate(config);
handler.initialize();
callback.waitForStatus(ThingStatus.OFFLINE);
ThingStatusInfo status = callback.getStatus();
assertEquals(ThingStatus.OFFLINE, status.getStatus(), "OFFLINE");
assertEquals(ThingStatusDetail.CONFIGURATION_ERROR, status.getStatusDetail(), "Config Error");
@@ -78,6 +79,7 @@ class TestWrongHandler {
handler.handleConfigurationUpdate(config);
handler.initialize();
callback.waitForStatus(ThingStatus.OFFLINE);
ThingStatusInfo status = callback.getStatus();
assertEquals(ThingStatus.OFFLINE, status.getStatus(), "OFFLINE");
assertEquals(ThingStatusDetail.GONE, status.getStatusDetail(), "Device disappeared");
@@ -13,13 +13,13 @@
package org.openhab.binding.dirigera.internal.handler.airpurifier;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu;
@@ -33,7 +33,6 @@ import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
@@ -47,31 +46,30 @@ class TestAirPurifier {
private static String deviceId = "a8319695-0729-428c-9465-aadc0b738995";
private static ThingTypeUID thingTypeUID = THING_TYPE_AIR_PURIFIER;
private static AirPurifierHandler handler = mock(AirPurifierHandler.class);
private static CallbackMock callback = mock(CallbackMock.class);
private static Thing thing = mock(Thing.class);
@Test
void testHandlerCreation() {
BaseHandler getHandler() {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json",
false, List.of());
ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId);
assertTrue(factoryHandler instanceof AirPurifierHandler);
handler = (AirPurifierHandler) factoryHandler;
thing = handler.getThing();
ThingHandlerCallback proxyCallback = handler.getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
callback = (CallbackMock) proxyCallback;
handler.initialize();
callback.waitForOnline();
assertTrue(factoryHandler instanceof BaseHandler);
return (BaseHandler) factoryHandler;
}
@Test
void testHandlerCreation() {
BaseHandler handler = getHandler();
assertNotNull(handler);
assertTrue(handler instanceof AirPurifierHandler);
assertTrue(handler instanceof BaseHandler);
assertNotNull(handler.getThing());
}
@Test
void testInitialization() {
testHandlerCreation();
BaseHandler handler = getHandler();
assertNotNull(handler);
Thing thing = handler.getThing();
assertNotNull(thing);
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(callback);
checkAirPurifierStates(callback);
@@ -92,29 +90,28 @@ class TestAirPurifier {
@Test
void testCommands() {
testHandlerCreation();
BaseHandler handler = getHandler();
assertNotNull(handler);
Thing thing = handler.getThing();
assertNotNull(thing);
DirigeraAPISimu api = (DirigeraAPISimu) handler.gateway().api();
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_MODE), new DecimalType(4));
String patch = DirigeraAPISimu.patchMap.get(deviceId);
String patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"fanMode\":\"on\"}}", patch, "Fan Mode on");
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_SPEED), new PercentType(23));
patch = DirigeraAPISimu.patchMap.get(deviceId);
patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"motorState\":12}}", patch, "Fan Speed");
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_PURIFIER_FAN_SPEED), new PercentType(100));
patch = DirigeraAPISimu.patchMap.get(deviceId);
patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"motorState\":50}}", patch, "Fan Speed");
}
@Test
void testDump() {
testHandlerCreation();
assertEquals("unit-test", handler.getToken());
}
void checkAirPurifierStates(CallbackMock callback) {
State otaStatus = callback.getState("dirigera:air-purifier:test-device:ota-status");
assertNotNull(otaStatus);
@@ -13,13 +13,13 @@
package org.openhab.binding.dirigera.internal.handler.blind;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu;
@@ -43,34 +43,29 @@ import org.openhab.core.types.State;
*/
@NonNullByDefault
class TestBlindHandler {
String deviceId = "eadfad54-9d23-4475-92b6-0ee3d6f8b481_1";
ThingTypeUID thingTypeUID = THING_TYPE_BLIND;
private static String deviceId = "eadfad54-9d23-4475-92b6-0ee3d6f8b481_1";
private static ThingTypeUID thingTypeUID = THING_TYPE_BLIND;
private static BlindHandler handler = mock(BlindHandler.class);
private static CallbackMock callback = mock(CallbackMock.class);
private static Thing thing = mock(Thing.class);
@Test
void testHandlerCreation() {
BaseHandler getHandler() {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json",
false, List.of());
ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId);
assertTrue(factoryHandler instanceof BlindHandler);
handler = (BlindHandler) factoryHandler;
thing = handler.getThing();
assertTrue(factoryHandler instanceof BaseHandler);
BaseHandler handler = (BlindHandler) factoryHandler;
ThingHandlerCallback proxyCallback = handler.getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
callback = (CallbackMock) proxyCallback;
handler.initialize();
callback.waitForOnline();
return handler;
}
@Test
void testInitialization() {
testHandlerCreation();
BaseHandler handler = getHandler();
assertNotNull(handler);
Thing thing = handler.getThing();
assertNotNull(thing);
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(callback);
checkBlindStates(callback);
@@ -86,10 +81,15 @@ class TestBlindHandler {
@Test
void testCommands() {
testHandlerCreation();
BaseHandler handler = getHandler();
assertNotNull(handler);
Thing thing = handler.getThing();
assertNotNull(thing);
DirigeraAPISimu api = (DirigeraAPISimu) handler.gateway().api();
// DirigeraAPISimu api = (DirigeraAPISimu) ((DirigeraHandler) hubBridge.getHandler()).api();
handler.handleCommand(new ChannelUID(thing.getUID(), "blind-level"), new PercentType(20));
String patch = DirigeraAPISimu.patchMap.get(deviceId);
String patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"blindsTargetLevel\":20}}", patch, "Target Blind Level");
}
@@ -13,7 +13,6 @@
package org.openhab.binding.dirigera.internal.handler.controller;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.util.Collections;
@@ -25,6 +24,7 @@ import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.FileReader;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu;
@@ -34,13 +34,12 @@ import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link TestDoubleShortcutController} Tests device handler creation, initializing and refresh of channels
@@ -49,36 +48,29 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
class TestDoubleShortcutController {
private final Logger logger = LoggerFactory.getLogger(TestDoubleShortcutController.class);
String deviceId = "854bdf30-86b8-48f5-b070-16ff5ab12be4_1";
ThingTypeUID thingTypeUID = THING_TYPE_DOUBLE_SHORTCUT_CONTROLLER;
private static DoubleShortcutControllerHandler handler = mock(DoubleShortcutControllerHandler.class);
private static CallbackMock callback = mock(CallbackMock.class);
private static Thing thing = mock(Thing.class);
@Test
void testHandlerCreation() {
BaseHandler getHandler() {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json",
false, List.of());
ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId);
assertTrue(factoryHandler instanceof DoubleShortcutControllerHandler);
handler = (DoubleShortcutControllerHandler) factoryHandler;
thing = handler.getThing();
assertTrue(factoryHandler instanceof BaseHandler);
BaseHandler handler = (BaseHandler) factoryHandler;
ThingHandlerCallback proxyCallback = handler.getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
callback = (CallbackMock) proxyCallback;
return handler;
}
@Test
void testInitialization() {
testHandlerCreation();
handler.initialize();
callback.waitForOnline();
checkStates(callback);
BaseHandler handler = getHandler();
Thing thing = handler.getThing();
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(callback);
callback.clear();
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_OTA_STATUS), RefreshType.REFRESH);
@@ -111,7 +103,10 @@ class TestDoubleShortcutController {
@Test
void testTriggers() {
testInitialization();
DoubleShortcutControllerHandler handler = (DoubleShortcutControllerHandler) getHandler();
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(callback);
String updateSequence = FileReader
.readFileInString("src/test/resources/websocket/scene-pressed/scene-trigger-sequence.json");
JSONArray sequence = new JSONArray(updateSequence);
@@ -131,13 +126,15 @@ class TestDoubleShortcutController {
@Test
void testRemoval() {
logger.warn("####### REMOVAL START ############");
DirigeraAPISimu.scenesAdded.clear();
DirigeraAPISimu.scenesDeleted.clear();
testTriggers();
logger.warn("####### TRIGGER CALLED ############");
BaseHandler handler = getHandler();
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(callback);
handler.dispose();
handler.handleRemoval();
callback.waitForStatus(ThingStatus.REMOVED);
Collections.sort(DirigeraAPISimu.scenesAdded);
Collections.sort(DirigeraAPISimu.scenesDeleted);
assertEquals(6, DirigeraAPISimu.scenesAdded.size(), "Scenes added size");
@@ -13,13 +13,13 @@
package org.openhab.binding.dirigera.internal.handler.controller;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.core.library.types.DecimalType;
@@ -44,29 +44,24 @@ class TestShortcutController {
String deviceId = "92dbcea1-3d7e-4d6a-a009-bdf3a1ae6691_1";
ThingTypeUID thingTypeUID = THING_TYPE_SINGLE_SHORTCUT_CONTROLLER;
private static ShortcutControllerHandler handler = mock(ShortcutControllerHandler.class);
private static CallbackMock callback = mock(CallbackMock.class);
private static Thing thing = mock(Thing.class);
@Test
void testHandlerCreation() {
BaseHandler getHandler() {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json",
false, List.of());
ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId);
assertTrue(factoryHandler instanceof ShortcutControllerHandler);
handler = (ShortcutControllerHandler) factoryHandler;
thing = handler.getThing();
assertTrue(factoryHandler instanceof BaseHandler);
BaseHandler handler = (ShortcutControllerHandler) factoryHandler;
ThingHandlerCallback proxyCallback = handler.getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
callback = (CallbackMock) proxyCallback;
handler.initialize();
callback.waitForOnline();
return handler;
}
@Test
void testInitialization() {
testHandlerCreation();
BaseHandler handler = getHandler();
Thing thing = handler.getThing();
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(handler);
assertNotNull(thing);
assertNotNull(callback);
@@ -13,7 +13,6 @@
package org.openhab.binding.dirigera.internal.handler.lights;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.util.List;
@@ -21,6 +20,7 @@ import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.handler.light.SwitchLightHandler;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
@@ -46,29 +46,24 @@ class TestSwitchLight {
String deviceId = "eb9a4367-9e23-4d37-9566-401a7ae7caf0_2";
ThingTypeUID thingTypeUID = THING_TYPE_SWITCH_LIGHT;
private static SwitchLightHandler handler = mock(SwitchLightHandler.class);
private static CallbackMock callback = mock(CallbackMock.class);
private static Thing thing = mock(Thing.class);
@Test
void testHandlerCreation() {
BaseHandler getHandler() {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json",
false, List.of());
ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId);
assertTrue(factoryHandler instanceof SwitchLightHandler);
handler = (SwitchLightHandler) factoryHandler;
thing = handler.getThing();
assertTrue(factoryHandler instanceof BaseHandler);
BaseHandler handler = (SwitchLightHandler) factoryHandler;
ThingHandlerCallback proxyCallback = handler.getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
callback = (CallbackMock) proxyCallback;
handler.initialize();
callback.waitForOnline();
return handler;
}
@Test
void testInitialization() {
testHandlerCreation();
BaseHandler handler = getHandler();
Thing thing = handler.getThing();
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(handler);
assertNotNull(thing);
assertNotNull(callback);
@@ -82,22 +77,22 @@ class TestSwitchLight {
@Test
void testCommands() {
DirigeraAPISimu.patchMap.clear();
testHandlerCreation();
// DirigeraAPISimu api = (DirigeraAPISimu) ((DirigeraHandler) hubBridge.getHandler()).api();
BaseHandler handler = getHandler();
Thing thing = handler.getThing();
DirigeraAPISimu api = (DirigeraAPISimu) handler.gateway().api();
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), OnOffType.ON);
DirigeraAPISimu.waitForPatch();
String patch = DirigeraAPISimu.patchMap.get(deviceId);
String patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"isOn\":true}}", patch, "Power on");
DirigeraAPISimu.patchMap.clear();
api.clear();
// simulate feedback
handler.handleUpdate(new JSONObject("{\"attributes\": {\"isOn\": true}}"));
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_POWER_STATE), OnOffType.OFF);
DirigeraAPISimu.waitForPatch();
patch = DirigeraAPISimu.patchMap.get(deviceId);
patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"isOn\":false}}", patch, "Power off");
}
@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dirigera.internal.handlerplug;
package org.openhab.binding.dirigera.internal.handler.plug;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
@@ -22,7 +22,6 @@ import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.handler.plug.PowerPlugHandler;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock;
import org.openhab.core.library.types.DecimalType;
@@ -10,32 +10,28 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dirigera.internal.handlerplug;
package org.openhab.binding.dirigera.internal.handler.plug;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.handler.plug.SimplePlugHandler;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.HandlerFactoryMock;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.storage.StorageService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.internal.ThingImpl;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
@@ -49,34 +45,27 @@ class TestSimplePlug {
String deviceId = "6379a590-dc0a-47b5-b35b-7b46dfefd282_1";
ThingTypeUID thingTypeUID = THING_TYPE_SIMPLE_PLUG;
@Test
void testHandlerCreation() {
HandlerFactoryMock hfm = new HandlerFactoryMock(mock(StorageService.class));
assertTrue(hfm.supportsThingType(thingTypeUID));
ThingImpl thing = new ThingImpl(thingTypeUID, "test-device");
ThingHandler th = hfm.createHandler(thing);
assertNotNull(th);
assertTrue(th instanceof SimplePlugHandler);
BaseHandler getHandler() {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json",
false, List.of());
ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId);
assertTrue(factoryHandler instanceof SimplePlugHandler);
assertTrue(factoryHandler instanceof BaseHandler);
BaseHandler handler = (SimplePlugHandler) factoryHandler;
ThingHandlerCallback proxyCallback = handler.getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
return handler;
}
@Test
void testInitialization() {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json",
false, List.of());
ThingImpl thing = new ThingImpl(thingTypeUID, "test-device");
thing.setBridgeUID(hubBridge.getBridgeUID());
SimplePlugHandler handler = new SimplePlugHandler(thing, SMART_PLUG_MAP);
CallbackMock callback = new CallbackMock();
callback.setBridge(hubBridge);
handler.setCallback(callback);
// set the right id
Map<String, Object> config = new HashMap<>();
config.put("id", deviceId);
handler.handleConfigurationUpdate(config);
handler.initialize();
callback.waitForOnline();
BaseHandler handler = getHandler();
Thing thing = handler.getThing();
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(handler);
assertNotNull(thing);
assertNotNull(callback);
checkPowerPlugStates(callback);
callback.clear();
@@ -105,10 +94,6 @@ class TestSimplePlug {
assertTrue(((QuantityType<?>) otaProgess).getUnit().equals(Units.PERCENT));
assertEquals(0, ((QuantityType<?>) otaProgess).intValue(), "OTA Progress");
State disableLightState = callback.getState("dirigera:simple-plug:test-device:disable-light");
assertNull(disableLightState);
State childlockState = callback.getState("dirigera:simple-plug:test-device:child-lock");
assertNull(childlockState);
State onOffState = callback.getState("dirigera:simple-plug:test-device:power");
assertNotNull(onOffState);
assertTrue(onOffState instanceof OnOffType);
@@ -10,18 +10,17 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dirigera.internal.handlerplug;
package org.openhab.binding.dirigera.internal.handler.plug;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.handler.BaseHandler;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.handler.plug.SmartPlugHandler;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu;
import org.openhab.core.library.types.DateTimeType;
@@ -48,29 +47,24 @@ class TestSmartPlug {
String deviceId = "ec549fa8-4e35-4f27-90e9-bb67e68311f2_1";
ThingTypeUID thingTypeUID = THING_TYPE_SMART_PLUG;
private static SmartPlugHandler handler = mock(SmartPlugHandler.class);
private static CallbackMock callback = mock(CallbackMock.class);
private static Thing thing = mock(Thing.class);
@Test
void testHandlerCreation() {
BaseHandler getHandler() {
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/devices/home-all-devices.json",
false, List.of());
ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(thingTypeUID, hubBridge, deviceId);
assertTrue(factoryHandler instanceof SmartPlugHandler);
handler = (SmartPlugHandler) factoryHandler;
thing = handler.getThing();
assertTrue(factoryHandler instanceof BaseHandler);
BaseHandler handler = (SmartPlugHandler) factoryHandler;
ThingHandlerCallback proxyCallback = handler.getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
callback = (CallbackMock) proxyCallback;
handler.initialize();
callback.waitForOnline();
return handler;
}
@Test
void testInitialization() {
testHandlerCreation();
BaseHandler handler = getHandler();
Thing thing = handler.getThing();
CallbackMock callback = (CallbackMock) handler.getCallback();
assertNotNull(handler);
assertNotNull(thing);
assertNotNull(callback);
@@ -94,17 +88,19 @@ class TestSmartPlug {
@Test
void testCommands() {
DirigeraAPISimu.patchMap.clear();
testHandlerCreation();
BaseHandler handler = getHandler();
Thing thing = handler.getThing();
CallbackMock callback = (CallbackMock) handler.getCallback();
DirigeraAPISimu api = (DirigeraAPISimu) handler.gateway().api();
assertNotNull(handler);
assertNotNull(thing);
assertNotNull(callback);
handler.handleCommand(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_RESET_DATE), new DateTimeType());
DirigeraAPISimu.waitForPatch();
String patch = DirigeraAPISimu.patchMap.get(deviceId);
String patch = api.getPatch(deviceId);
assertNotNull(patch);
assertEquals("{\"attributes\":{\"energyConsumedAtLastReset\":0}}", patch, "Reset Power");
DirigeraAPISimu.patchMap.clear();
}
void checkSmartPlugStates(CallbackMock callback) {
@@ -49,22 +49,76 @@ import org.openhab.core.types.TimeSeries;
*/
@NonNullByDefault
public class CallbackMock implements ThingHandlerCallback {
private final int STATUS_DURATION_TIMEOUT_SEC = 10;
private ThingStatusInfo statusInfo = ThingStatusInfoBuilder.create(ThingStatus.UNINITIALIZED).build();
private @Nullable Bridge bridge;
private ThingStatusInfo status = ThingStatusInfoBuilder.create(ThingStatus.OFFLINE).build();
public Map<String, State> stateMap = new HashMap<>();
public Map<String, String> triggerMap = new HashMap<>();
@Override
public void statusUpdated(Thing thing, ThingStatusInfo thingStatusInfo) {
synchronized (this) {
thing.setStatusInfo(thingStatusInfo);
statusInfo = thingStatusInfo;
this.notifyAll();
}
}
public void waitForStatus(ThingStatus expectedStatus) {
synchronized (this) {
Instant start = Instant.now();
Instant check = Instant.now();
while (!expectedStatus.equals(statusInfo.getStatus())
&& Duration.between(start, check).getSeconds() < STATUS_DURATION_TIMEOUT_SEC) {
try {
this.wait(100);
} catch (InterruptedException e) {
fail(e.getMessage());
}
check = Instant.now();
}
}
// if method is exited without reaching ONLINE e.g. through timeout fail
if (!expectedStatus.equals(statusInfo.getStatus())) {
fail("Wait for status " + expectedStatus + " reached just " + statusInfo);
}
}
public void waitForOnline() {
waitForStatus(ThingStatus.ONLINE);
}
public ThingStatusInfo getStatus() {
return statusInfo;
}
public void clear() {
stateMap.clear();
}
public @Nullable State getState(String channel) {
synchronized (stateMap) {
Instant start = Instant.now();
Instant check = Instant.now();
while (stateMap.get(channel) == null
&& Duration.between(start, check).getSeconds() < STATUS_DURATION_TIMEOUT_SEC) {
try {
stateMap.wait(100);
} catch (InterruptedException e) {
fail("Interruppted waiting for ONLINE");
}
check = Instant.now();
}
}
return stateMap.get(channel);
}
@Override
public void stateUpdated(ChannelUID channelUID, State state) {
stateMap.put(channelUID.getAsString(), state);
synchronized (stateMap) {
stateMap.put(channelUID.getAsString(), state);
stateMap.notifyAll();
}
}
@Override
@@ -75,37 +129,6 @@ public class CallbackMock implements ThingHandlerCallback {
public void sendTimeSeries(ChannelUID channelUID, TimeSeries timeSeries) {
}
@Override
public void statusUpdated(Thing thing, ThingStatusInfo thingStatus) {
synchronized (this) {
status = thingStatus;
this.notifyAll();
}
}
public ThingStatusInfo getStatus() {
return status;
}
public void waitForOnline() {
synchronized (this) {
Instant start = Instant.now();
Instant check = Instant.now();
while (!ThingStatus.ONLINE.equals(status.getStatus()) && Duration.between(start, check).getSeconds() < 10) {
try {
this.wait(1000);
} catch (InterruptedException e) {
fail("Interruppted waiting for ONLINE");
}
check = Instant.now();
}
}
// if method is exited without reaching ONLINE e.g. through timeout fail
if (!ThingStatus.ONLINE.equals(status.getStatus())) {
fail("waitForOnline just reached status " + status);
}
}
@Override
public void thingUpdated(Thing thing) {
}
@@ -14,6 +14,8 @@ package org.openhab.binding.dirigera.internal.mock;
import static org.junit.jupiter.api.Assertions.fail;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@@ -43,24 +45,30 @@ public class DicoveryServiceMock extends DirigeraDiscoveryService {
@Override
public void deviceRemoved(final DiscoveryResult discoveryResult) {
// logger.warn("Discovery thingRemoved {}", discoveryResult);
String id = discoveryResult.getThingUID().getId();
DiscoveryResult remover = discoveries.remove(id);
// assertNotNull(remover);
if (remover != null) {
deletes.put(id, remover);
}
}
public void waitForDetection() {
synchronized (this) {
if (discoveries.isEmpty()) {
try {
wait(5000);
} catch (InterruptedException e) {
fail(e.getMessage());
}
DiscoveryResult remover = discoveries.remove(id);
if (remover != null) {
deletes.put(id, remover);
}
}
}
public void waitForDetection(int size) {
synchronized (this) {
Instant start = Instant.now();
Instant check = Instant.now();
while (discoveries.size() != size && Duration.between(start, check).getSeconds() < 5) {
try {
wait(100);
} catch (InterruptedException e) {
fail(e.getMessage());
}
check = Instant.now();
}
}
if (discoveries.size() != size) {
fail("Discovery size expected " + size + " but was " + discoveries.size());
}
}
}
@@ -19,6 +19,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -26,6 +27,7 @@ import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -44,16 +46,20 @@ import org.openhab.core.types.UnDefType;
*/
@NonNullByDefault
public class DirigeraAPISimu implements DirigeraAPI {
private static JSONObject model = new JSONObject();
private JSONObject model = new JSONObject();
public static String fileName = "src/test/resources/home/home.json";
public static Map<String, String> patchMap = new HashMap<>();
private String fileName = "src/test/resources/home/home.json";
private Map<String, String> patchMap = new HashMap<>();
public static List<String> scenesAdded = new ArrayList<>();
public static List<String> scenesDeleted = new ArrayList<>();
public DirigeraAPISimu(HttpClient client, Gateway gateway) {
}
public DirigeraAPISimu(HttpClient client, Gateway gateway, String homeFile) {
fileName = homeFile;
}
@Override
public JSONObject readHome() {
String modelString = FileReader.readFileInString(fileName);
@@ -136,15 +142,25 @@ public class DirigeraAPISimu implements DirigeraAPI {
scenesDeleted.add(uuid);
}
public static void waitForPatch() {
public @Nullable String getPatch(String id) {
Instant endTime = Instant.now().plusSeconds(10);
synchronized (patchMap) {
if (patchMap.isEmpty()) {
String patch = patchMap.get(id);
while (patch == null && Instant.now().isBefore(endTime)) {
try {
patchMap.wait(5000);
patchMap.wait(100);
patch = patchMap.get(id);
} catch (InterruptedException e) {
fail();
}
}
return patch;
}
}
public void clear() {
patchMap.clear();
scenesAdded.clear();
scenesDeleted.clear();
}
}
@@ -13,22 +13,28 @@
package org.openhab.binding.dirigera.internal.model;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.openhab.binding.dirigera.internal.Constants.*;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.client.HttpClient;
import org.junit.jupiter.api.Test;
import org.openhab.binding.dirigera.internal.FileReader;
import org.openhab.binding.dirigera.internal.handler.DirigeraBridgeProvider;
import org.openhab.binding.dirigera.internal.handler.DirigeraHandler;
import org.openhab.binding.dirigera.internal.handler.DirigeraHandlerManipulator;
import org.openhab.binding.dirigera.internal.handler.sensor.WaterSensorHandler;
import org.openhab.binding.dirigera.internal.interfaces.Gateway;
import org.openhab.binding.dirigera.internal.mock.CallbackMock;
import org.openhab.binding.dirigera.internal.mock.DicoveryServiceMock;
import org.openhab.binding.dirigera.internal.mock.DirigeraAPISimu;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerCallback;
@@ -147,30 +153,42 @@ class TestModel {
@Test
void testDiscoveryAfterHandlerRemoval() {
DirigeraHandler.detectionTimeSeonds = 0;
Bridge hubBridge = DirigeraBridgeProvider.prepareSimuBridge("src/test/resources/home/home-one-device.json",
true, List.of());
Gateway gateway = (Gateway) hubBridge.getHandler();
assertNotNull(gateway);
DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery();
discovery.waitForDetection(1);
assertEquals(1, discovery.discoveries.size(), "Initial discoveries");
String deviceId = "9af826ad-a8ad-40bf-8aed-125300bccd20_1";
ThingHandler factoryHandler = DirigeraBridgeProvider.createHandler(THING_TYPE_WATER_SENSOR, hubBridge,
"9af826ad-a8ad-40bf-8aed-125300bccd20_1");
deviceId);
assertTrue(factoryHandler instanceof WaterSensorHandler);
WaterSensorHandler handler = (WaterSensorHandler) factoryHandler;
ThingHandlerCallback proxyCallback = handler.getCallback();
assertNotNull(proxyCallback);
assertTrue(proxyCallback instanceof CallbackMock);
CallbackMock callback = (CallbackMock) proxyCallback;
callback.waitForOnline();
DirigeraHandler.detectionTimeSeonds = 0;
discovery.discoveries.clear();
assertEquals(0, discovery.discoveries.size(), "Cleanup after handler creation");
// wait until device is registered
Instant start = Instant.now();
Instant check = Instant.now();
while (!gateway.isKnownDevice(deviceId) && Duration.between(start, check).getSeconds() < 10) {
try {
Thread.sleep(100);
check = Instant.now();
} catch (InterruptedException e) {
fail(e.getMessage());
}
}
handler.dispose();
handler.handleRemoval();
discovery.waitForDetection();
callback.waitForStatus(ThingStatus.REMOVED);
discovery.waitForDetection(1);
assertEquals(1, discovery.discoveries.size(), "After removal new discovery result shall be present ");
}
@@ -192,7 +210,7 @@ class TestModel {
void testDeviceAdded() {
Bridge hubBridge = DirigeraBridgeProvider
.prepareSimuBridge("src/test/resources/websocket/device-added/home-before.json", true, List.of());
Gateway gateway = (Gateway) hubBridge.getHandler();
DirigeraHandlerManipulator gateway = (DirigeraHandlerManipulator) hubBridge.getHandler();
assertNotNull(gateway);
DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery();
@@ -201,7 +219,9 @@ class TestModel {
// Prepare update message
String update = FileReader.readFileInString("src/test/resources/websocket/device-added/device-added.json");
// prepare mock
DirigeraAPISimu.fileName = "src/test/resources/websocket/device-added/home-after.json";
DirigeraAPISimu apiSimu = new DirigeraAPISimu(mock(HttpClient.class), gateway,
"src/test/resources/websocket/device-added/home-after.json");
gateway.setAPIHandler(apiSimu);
try {
gateway.websocketUpdate(update);
// give the gateway some time to handle the message
@@ -216,7 +236,7 @@ class TestModel {
void testDeviceRemoved() {
Bridge hubBridge = DirigeraBridgeProvider
.prepareSimuBridge("src/test/resources/websocket/device-removed/home-before.json", true, List.of());
Gateway gateway = (Gateway) hubBridge.getHandler();
DirigeraHandlerManipulator gateway = (DirigeraHandlerManipulator) hubBridge.getHandler();
assertNotNull(gateway);
DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery();
@@ -225,7 +245,9 @@ class TestModel {
// Prepare update message
String update = FileReader.readFileInString("src/test/resources/websocket/device-removed/device-removed.json");
// prepare mock
DirigeraAPISimu.fileName = "src/test/resources/websocket/device-removed/home-after.json";
DirigeraAPISimu apiSimu = new DirigeraAPISimu(mock(HttpClient.class), gateway,
"src/test/resources/websocket/device-removed/home-after.json");
gateway.setAPIHandler(apiSimu);
try {
gateway.websocketUpdate(update);
// give the gateway some time to handle the message
@@ -241,7 +263,7 @@ class TestModel {
void testSceneCreated() {
Bridge hubBridge = DirigeraBridgeProvider
.prepareSimuBridge("src/test/resources/websocket/scene-created/home-before.json", true, List.of());
Gateway gateway = (Gateway) hubBridge.getHandler();
DirigeraHandlerManipulator gateway = (DirigeraHandlerManipulator) hubBridge.getHandler();
assertNotNull(gateway);
DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery();
@@ -250,7 +272,9 @@ class TestModel {
// Prepare update message
String update = FileReader.readFileInString("src/test/resources/websocket/scene-created/scene-created.json");
// prepare mock
DirigeraAPISimu.fileName = "src/test/resources/websocket/scene-created/home-after.json";
DirigeraAPISimu apiSimu = new DirigeraAPISimu(mock(HttpClient.class), gateway,
"src/test/resources/websocket/scene-created/home-after.json");
gateway.setAPIHandler(apiSimu);
try {
gateway.websocketUpdate(update);
// give the gateway some time to handle the message
@@ -265,7 +289,7 @@ class TestModel {
void testSceneDeleted() {
Bridge hubBridge = DirigeraBridgeProvider
.prepareSimuBridge("src/test/resources/websocket/scene-deleted/home-before.json", true, List.of());
Gateway gateway = (Gateway) hubBridge.getHandler();
DirigeraHandlerManipulator gateway = (DirigeraHandlerManipulator) hubBridge.getHandler();
assertNotNull(gateway);
DicoveryServiceMock discovery = (DicoveryServiceMock) gateway.discovery();
@@ -274,7 +298,9 @@ class TestModel {
// Prepare update message
String update = FileReader.readFileInString("src/test/resources/websocket/scene-deleted/scene-deleted.json");
// prepare mock
DirigeraAPISimu.fileName = "src/test/resources/websocket/scene-deleted/home-after.json";
DirigeraAPISimu apiSimu = new DirigeraAPISimu(mock(HttpClient.class), gateway,
"src/test/resources/websocket/scene-deleted/home-after.json");
gateway.setAPIHandler(apiSimu);
try {
gateway.websocketUpdate(update);
// give the gateway some time to handle the message