diff --git a/bom/compile-model/pom.xml b/bom/compile-model/pom.xml
index fd690836e48..d6ae09dd8c7 100644
--- a/bom/compile-model/pom.xml
+++ b/bom/compile-model/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bomorg.openhab.core.reactor.bom
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom.compile-model
diff --git a/bom/compile/pom.xml b/bom/compile/pom.xml
index d4fcbe2b3f8..bb4af3a4576 100644
--- a/bom/compile/pom.xml
+++ b/bom/compile/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bomorg.openhab.core.reactor.bom
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom.compile
diff --git a/bom/openhab-core-index/pom.xml b/bom/openhab-core-index/pom.xml
index 95effdb3115..3ae7836e326 100644
--- a/bom/openhab-core-index/pom.xml
+++ b/bom/openhab-core-index/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bomorg.openhab.core.reactor.bom
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom.openhab-core-index
diff --git a/bom/openhab-core/pom.xml b/bom/openhab-core/pom.xml
index 905bfb7e0fd..51e2179909f 100644
--- a/bom/openhab-core/pom.xml
+++ b/bom/openhab-core/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bomorg.openhab.core.reactor.bom
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom.openhab-core
@@ -328,6 +328,12 @@
${project.version}compile
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.addon.sddp
+ ${project.version}
+ compile
+ org.openhab.core.bundlesorg.openhab.core.config.discovery.addon.upnp
@@ -346,6 +352,12 @@
${project.version}compile
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.sddp
+ ${project.version}
+ compile
+ org.openhab.core.bundlesorg.openhab.core.config.discovery.usbserial
diff --git a/bom/pom.xml b/bom/pom.xml
index c8c08e84cf1..22c0f0450aa 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -7,7 +7,7 @@
org.openhab.coreorg.openhab.core.reactor
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom
diff --git a/bom/runtime-index/pom.xml b/bom/runtime-index/pom.xml
index b2d08cf386c..31b6b2d1b38 100644
--- a/bom/runtime-index/pom.xml
+++ b/bom/runtime-index/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bomorg.openhab.core.reactor.bom
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom.runtime-index
diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml
index d04315cbeac..24af95cdb4a 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bomorg.openhab.core.reactor.bom
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom.runtime
@@ -794,7 +794,7 @@
com.google.guavaguava
- 33.1.0-jre
+ 33.2.0-jrecompile
diff --git a/bom/test-index/pom.xml b/bom/test-index/pom.xml
index fe176069958..6f921754ce5 100644
--- a/bom/test-index/pom.xml
+++ b/bom/test-index/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bomorg.openhab.core.reactor.bom
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom.test-index
diff --git a/bom/test/pom.xml b/bom/test/pom.xml
index 3f0266a9ee8..9985dc6976f 100644
--- a/bom/test/pom.xml
+++ b/bom/test/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bomorg.openhab.core.reactor.bom
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.bom.test
diff --git a/bundles/org.openhab.core.addon.eclipse/pom.xml b/bundles/org.openhab.core.addon.eclipse/pom.xml
index 1b519d2e27a..3dc2a6ae9be 100644
--- a/bundles/org.openhab.core.addon.eclipse/pom.xml
+++ b/bundles/org.openhab.core.addon.eclipse/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.addon.eclipse
diff --git a/bundles/org.openhab.core.addon.marketplace.karaf/pom.xml b/bundles/org.openhab.core.addon.marketplace.karaf/pom.xml
index ee04295b653..fbfa7a3a57f 100644
--- a/bundles/org.openhab.core.addon.marketplace.karaf/pom.xml
+++ b/bundles/org.openhab.core.addon.marketplace.karaf/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.addon.marketplace.karaf
diff --git a/bundles/org.openhab.core.addon.marketplace/pom.xml b/bundles/org.openhab.core.addon.marketplace/pom.xml
index 661a51c1764..c890682cf6f 100644
--- a/bundles/org.openhab.core.addon.marketplace/pom.xml
+++ b/bundles/org.openhab.core.addon.marketplace/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.addon.marketplace
diff --git a/bundles/org.openhab.core.addon.marketplace/src/main/resources/OH-INF/i18n/marketplace_fr.properties b/bundles/org.openhab.core.addon.marketplace/src/main/resources/OH-INF/i18n/marketplace_fr.properties
index 6f119407325..44c8248ad40 100644
--- a/bundles/org.openhab.core.addon.marketplace/src/main/resources/OH-INF/i18n/marketplace_fr.properties
+++ b/bundles/org.openhab.core.addon.marketplace/src/main/resources/OH-INF/i18n/marketplace_fr.properties
@@ -5,7 +5,7 @@ system.config.jsonaddonservice.urls.description = Liste d'URLs séparées par un
system.config.marketplace.apiKey.label = Clé API pour community.openhab.org
system.config.marketplace.apiKey.description = Spécifiez la clé API à utiliser sur le forum de la communauté (pour les administrateurs - cela permet par exemple de voir le contenu qui n'a pas encore fait l'objet de revue ou autrement caché au grand public). Laissez vide si vous n'en avez pas.
system.config.marketplace.enable.label = Activer le marketplace de la communauté
-system.config.marketplace.enable.description = Si désactivé, aucune extension du marketplace de la communautaire ne sera affichée. Les extensions déjà installées seront toujours disponibles.
+system.config.marketplace.enable.description = Si désactivé, aucune extension du marketplace de la communauté ne sera affichée. Les extensions déjà installées seront toujours disponibles.
system.config.marketplace.showUnpublished.label = Afficher les entrées non publiées
system.config.marketplace.showUnpublished.description = Inclure les entrées qui n'ont pas été taguées comme publiées. Attention \: ceci peut inclure des entrées qui ne sont pas prêtes et qui pourraient ne pas fonctionner ou nuire à votre installation. Activez à vos risques et périls, uniquement pour les tests.
diff --git a/bundles/org.openhab.core.addon/pom.xml b/bundles/org.openhab.core.addon/pom.xml
index 24d774631cc..fe58a720020 100644
--- a/bundles/org.openhab.core.addon/pom.xml
+++ b/bundles/org.openhab.core.addon/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.addon
diff --git a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java
index 2815fe8a0d1..d21b9830411 100644
--- a/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java
+++ b/bundles/org.openhab.core.addon/src/main/java/org/openhab/core/addon/AddonInfoRegistry.java
@@ -106,8 +106,8 @@ public void removeAddonInfoProvider(AddonInfoProvider addonInfoProvider) {
builder.withName(b.getName());
builder.withDescription(b.getDescription());
}
- if (!(a.isMasterAddonInfo() || b.isMasterAddonInfo())) {
- builder.isMasterAddonInfo(false);
+ if (!a.isMasterAddonInfo() && b.isMasterAddonInfo()) {
+ builder.isMasterAddonInfo(true);
}
if (a.getConnection() == null && b.getConnection() != null) {
builder.withConnection(b.getConnection());
diff --git a/bundles/org.openhab.core.audio/pom.xml b/bundles/org.openhab.core.audio/pom.xml
index 9ca28870bf5..317cc4c8dfc 100644
--- a/bundles/org.openhab.core.audio/pom.xml
+++ b/bundles/org.openhab.core.audio/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.audio
diff --git a/bundles/org.openhab.core.auth.jaas/pom.xml b/bundles/org.openhab.core.auth.jaas/pom.xml
index 78a75892340..8d647051bb1 100644
--- a/bundles/org.openhab.core.auth.jaas/pom.xml
+++ b/bundles/org.openhab.core.auth.jaas/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.auth.jaas
diff --git a/bundles/org.openhab.core.auth.oauth2client/pom.xml b/bundles/org.openhab.core.auth.oauth2client/pom.xml
index 0eb0a93e4ef..8b5454ba1c2 100644
--- a/bundles/org.openhab.core.auth.oauth2client/pom.xml
+++ b/bundles/org.openhab.core.auth.oauth2client/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.auth.oauth2client
diff --git a/bundles/org.openhab.core.automation.module.media/pom.xml b/bundles/org.openhab.core.automation.module.media/pom.xml
index 4bbad43616f..3beab276aae 100644
--- a/bundles/org.openhab.core.automation.module.media/pom.xml
+++ b/bundles/org.openhab.core.automation.module.media/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.automation.module.media
diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/pom.xml b/bundles/org.openhab.core.automation.module.script.rulesupport/pom.xml
index 241ad616912..05cc446df67 100644
--- a/bundles/org.openhab.core.automation.module.script.rulesupport/pom.xml
+++ b/bundles/org.openhab.core.automation.module.script.rulesupport/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.automation.module.script.rulesupport
diff --git a/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/resources/OH-INF/i18n/jsr223_nl.properties b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/resources/OH-INF/i18n/jsr223_nl.properties
new file mode 100644
index 00000000000..674e6faee13
--- /dev/null
+++ b/bundles/org.openhab.core.automation.module.script.rulesupport/src/main/resources/OH-INF/i18n/jsr223_nl.properties
@@ -0,0 +1,21 @@
+# jsr223.ScriptedAction
+
+module-type.jsr223.ScriptedAction.label = Actie script
+module-type.jsr223.ScriptedAction.description = maakt het uitvoeren van een method die is gedefinieerd door een script mogelijk
+module-type.jsr223.ScriptedAction.config.privId.description = de identifier van de private method
+module-type.jsr223.ScriptedAction.output.result.label = resultaat
+module-type.jsr223.ScriptedAction.output.result.description = het resultaat van het script.
+
+# jsr223.ScriptedCondition
+
+module-type.jsr223.ScriptedCondition.label = Voorwaarde script
+module-type.jsr223.ScriptedCondition.description = maakt de definitie van een voorwaarde met een script mogelijk
+module-type.jsr223.ScriptedCondition.config.privId.description = de identifier van de private method
+
+# jsr223.ScriptedTrigger
+
+module-type.jsr223.ScriptedTrigger.label = Trigger script
+module-type.jsr223.ScriptedTrigger.description = maakt het uitvoeren van een method die is gedefinieerd door een script mogelijk
+module-type.jsr223.ScriptedTrigger.config.privId.description = de identifier van de private method
+module-type.jsr223.ScriptedTrigger.output.triggerOutput.label = TriggerOutput label
+module-type.jsr223.ScriptedTrigger.output.triggerOutput.description = Tekst van de trigger
diff --git a/bundles/org.openhab.core.automation.module.script/pom.xml b/bundles/org.openhab.core.automation.module.script/pom.xml
index 011647661b2..18e300b2688 100644
--- a/bundles/org.openhab.core.automation.module.script/pom.xml
+++ b/bundles/org.openhab.core.automation.module.script/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.automation.module.script
diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/action/TimerImpl.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/action/TimerImpl.java
index 159e73887e3..07496611044 100644
--- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/action/TimerImpl.java
+++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/action/TimerImpl.java
@@ -30,7 +30,6 @@
public class TimerImpl implements Timer {
private final Scheduler scheduler;
- private final ZonedDateTime startTime;
private final SchedulerRunnable runnable;
private final @Nullable String identifier;
private ScheduledCompletableFuture> future;
@@ -42,7 +41,6 @@ public TimerImpl(Scheduler scheduler, ZonedDateTime startTime, SchedulerRunnable
public TimerImpl(Scheduler scheduler, ZonedDateTime startTime, SchedulerRunnable runnable,
@Nullable String identifier) {
this.scheduler = scheduler;
- this.startTime = startTime;
this.runnable = runnable;
this.identifier = identifier;
@@ -78,7 +76,7 @@ public boolean isCancelled() {
@Override
public boolean isRunning() {
- return isActive() && ZonedDateTime.now().isAfter(startTime);
+ return isActive() && ZonedDateTime.now().isAfter(future.getScheduledTime());
}
@Override
diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java
index de3661021eb..6a695d6e7ba 100644
--- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java
+++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/AbstractScriptModuleHandler.java
@@ -18,10 +18,14 @@
import java.util.Optional;
import java.util.UUID;
+import javax.script.Compilable;
+import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
+import javax.script.ScriptException;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.Module;
import org.openhab.core.automation.handler.BaseModuleHandler;
import org.openhab.core.automation.module.script.ScriptEngineContainer;
@@ -35,6 +39,7 @@
*
* @author Kai Kreuzer - Initial contribution
* @author Simon Merschjohann - Initial contribution
+ * @author Florian Hotze - Add support for script pre-compilation
*
* @param the type of module the concrete handler can handle
*/
@@ -54,6 +59,7 @@ public abstract class AbstractScriptModuleHandler extends Base
private final String engineIdentifier;
private Optional scriptEngine = Optional.empty();
+ private Optional compiledScript = Optional.empty();
private final String type;
protected final String script;
@@ -80,6 +86,30 @@ private static String getValidConfigParameter(String parameter, Configuration co
}
}
+ /**
+ * Creates the {@link ScriptEngine} and compiles the script if the {@link ScriptEngine} implements
+ * {@link Compilable}.
+ */
+ protected void compileScript() throws ScriptException {
+ if (compiledScript.isPresent()) {
+ return;
+ }
+ if (!scriptEngineManager.isSupported(this.type)) {
+ logger.debug(
+ "ScriptEngine for language '{}' could not be found, skipping compilation of script for identifier: {}",
+ type, engineIdentifier);
+ return;
+ }
+ Optional engine = getScriptEngine();
+ if (engine.isPresent()) {
+ ScriptEngine scriptEngine = engine.get();
+ if (scriptEngine instanceof Compilable) {
+ logger.debug("Pre-compiling script of rule with UID '{}'", ruleUID);
+ compiledScript = Optional.ofNullable(((Compilable) scriptEngine).compile(script));
+ }
+ }
+ }
+
@Override
public void dispose() {
scriptEngineManager.removeEngine(engineIdentifier);
@@ -169,4 +199,26 @@ protected void resetExecutionContext(ScriptEngine engine, Map context
executionContext.removeAttribute(key, ScriptContext.ENGINE_SCOPE);
}
}
+
+ /**
+ * Evaluates the passed script with the ScriptEngine.
+ *
+ * @param engine the script engine that is used
+ * @param script the script to evaluate
+ * @return the value returned from the execution of the script
+ */
+ protected @Nullable Object eval(ScriptEngine engine, String script) {
+ try {
+ if (compiledScript.isPresent()) {
+ logger.debug("Executing pre-compiled script of rule with UID '{}'", ruleUID);
+ return compiledScript.get().eval(engine.getContext());
+ }
+ logger.debug("Executing script of rule with UID '{}'", ruleUID);
+ return engine.eval(script);
+ } catch (ScriptException e) {
+ logger.error("Script execution of rule with UID '{}' failed: {}", ruleUID, e.getMessage(),
+ logger.isDebugEnabled() ? e : null);
+ return null;
+ }
+ }
}
diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java
index 772a0ced3f2..a170683b881 100644
--- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java
+++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptActionHandler.java
@@ -31,6 +31,7 @@
*
* @author Kai Kreuzer - Initial contribution
* @author Simon Merschjohann - Initial contribution
+ * @author Florian Hotze - Add support for script pre-compilation
*/
@NonNullByDefault
public class ScriptActionHandler extends AbstractScriptModuleHandler implements ActionHandler {
@@ -61,6 +62,11 @@ public void dispose() {
super.dispose();
}
+ @Override
+ public void compile() throws ScriptException {
+ super.compileScript();
+ }
+
@Override
public @Nullable Map execute(final Map context) {
Map resultMap = new HashMap<>();
@@ -71,13 +77,8 @@ public void dispose() {
getScriptEngine().ifPresent(scriptEngine -> {
setExecutionContext(scriptEngine, context);
- try {
- Object result = scriptEngine.eval(script);
- resultMap.put("result", result);
- } catch (ScriptException e) {
- logger.error("Script execution of rule with UID '{}' failed: {}", ruleUID, e.getMessage(),
- logger.isDebugEnabled() ? e : null);
- }
+ Object result = eval(scriptEngine, script);
+ resultMap.put("result", result);
resetExecutionContext(scriptEngine, context);
});
diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java
index d25ff4e1077..a51ad384195 100644
--- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java
+++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/handler/ScriptConditionHandler.java
@@ -30,6 +30,7 @@
*
* @author Kai Kreuzer - Initial contribution
* @author Simon Merschjohann - Initial contribution
+ * @author Florian Hotze - Add support for script pre-compilation
*/
@NonNullByDefault
public class ScriptConditionHandler extends AbstractScriptModuleHandler implements ConditionHandler {
@@ -42,6 +43,11 @@ public ScriptConditionHandler(Condition module, String ruleUID, ScriptEngineMana
super(module, ruleUID, scriptEngineManager);
}
+ @Override
+ public void compile() throws ScriptException {
+ super.compileScript();
+ }
+
@Override
public boolean isSatisfied(final Map context) {
boolean result = false;
@@ -55,18 +61,14 @@ public boolean isSatisfied(final Map context) {
if (engine.isPresent()) {
ScriptEngine scriptEngine = engine.get();
setExecutionContext(scriptEngine, context);
- try {
- Object returnVal = scriptEngine.eval(script);
- if (returnVal instanceof Boolean boolean1) {
- result = boolean1;
- } else {
- logger.error("Script of rule with UID '{}' did not return a boolean value, but '{}'", ruleUID,
- returnVal);
- }
- } catch (ScriptException e) {
- logger.error("Script execution of rule with UID '{}' failed: {}", ruleUID, e.getMessage(),
- logger.isDebugEnabled() ? e : null);
+ Object returnVal = eval(scriptEngine, script);
+ if (returnVal instanceof Boolean boolean1) {
+ result = boolean1;
+ } else {
+ logger.error("Script of rule with UID '{}' did not return a boolean value, but '{}'", ruleUID,
+ returnVal);
}
+ resetExecutionContext(scriptEngine, context);
}
return result;
diff --git a/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_nl.properties b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_nl.properties
new file mode 100644
index 00000000000..7b6a19d0f75
--- /dev/null
+++ b/bundles/org.openhab.core.automation.module.script/src/main/resources/OH-INF/i18n/scriptprofile_nl.properties
@@ -0,0 +1,8 @@
+profile.config.transform.SCRIPT.commandFromItemScript.label = Item Command Naar Thing Transformatie
+profile.config.transform.SCRIPT.commandFromItemScript.description = Het Script voor het transformeren van commands van het item naar de Thing handler. Het script kan null retourneren om de commands te verwijderen en ze niet door te geven.
+profile.config.transform.SCRIPT.stateFromItemScript.label = Item Waarde Naar Thing Transformatie
+profile.config.transform.SCRIPT.stateFromItemScript.description = Het Script voor het transformeren van waarden van het item naar de Thing handler. Het script kan null retourneren om de waarden te verwijderen en ze niet door te geven.
+profile.config.transform.SCRIPT.toHandlerScript.label = Item Naar Thing Transformatie (DEPRECATED)
+profile.config.transform.SCRIPT.toHandlerScript.description = Het script voor het transformeren van commands van het item naar de Thing handler. Het script kan null terugsturen om de commands te verwijderen en ze niet door te geven. Dit wordt niet meer ondersteund en is vervangen door 'commandFromItemScript'.
+profile.config.transform.SCRIPT.toItemScript.label = Thing Naar Item Transformatie
+profile.config.transform.SCRIPT.toItemScript.description = Het Script voor het transformeren van waarde updates en commands van de Thing handler naar het item. Het script kan null retourneren om de waarde updates/commands te verwijderen en ze niet door te geven.
diff --git a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/TimerImplTest.java b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/TimerImplTest.java
index 827b3b35373..a96c7a08497 100644
--- a/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/TimerImplTest.java
+++ b/bundles/org.openhab.core.automation.module.script/src/test/java/org/openhab/core/automation/module/script/TimerImplTest.java
@@ -111,6 +111,12 @@ public void testTimerIsRunning() throws InterruptedException {
assertThat(subject.isRunning(), is(false));
assertThat(subject.hasTerminated(), is(true));
assertThat(subject.isCancelled(), is(false));
+
+ subject.reschedule(ZonedDateTime.now().plusSeconds(DEFAULT_TIMEOUT_SECONDS));
+ assertThat(subject.isRunning(), is(false));
+
+ Thread.sleep(TimeUnit.SECONDS.toMillis(DEFAULT_TIMEOUT_SECONDS) + 500);
+ assertThat(subject.isRunning(), is(true));
}
private Timer createTimer(ZonedDateTime instant, SchedulerRunnable runnable) {
diff --git a/bundles/org.openhab.core.automation.rest/pom.xml b/bundles/org.openhab.core.automation.rest/pom.xml
index f74d4ec9a3a..1d5dd282324 100644
--- a/bundles/org.openhab.core.automation.rest/pom.xml
+++ b/bundles/org.openhab.core.automation.rest/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.automation.rest
diff --git a/bundles/org.openhab.core.automation/pom.xml b/bundles/org.openhab.core.automation/pom.xml
index 18c4bcd6242..d9526ec7d46 100644
--- a/bundles/org.openhab.core.automation/pom.xml
+++ b/bundles/org.openhab.core.automation/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.automation
diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ActionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ActionHandler.java
index 355dba470c6..bc3599ee9eb 100644
--- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ActionHandler.java
+++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ActionHandler.java
@@ -31,6 +31,14 @@
*/
@NonNullByDefault
public interface ActionHandler extends ModuleHandler {
+ /**
+ * Called to compile an {@link Action} of the {@link Rule} when the rule is initialized.
+ *
+ * @throws Exception if the compilation fails
+ */
+ default void compile() throws Exception {
+ // Do nothing by default
+ }
/**
* Called to execute an {@link Action} of the {@link Rule} when it is needed.
diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ConditionHandler.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ConditionHandler.java
index 7adc0b6ce2d..5bb5cf4bb00 100644
--- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ConditionHandler.java
+++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/handler/ConditionHandler.java
@@ -29,6 +29,14 @@
*/
@NonNullByDefault
public interface ConditionHandler extends ModuleHandler {
+ /**
+ * Called to compile the {@link Condition} when the {@link Rule} is initialized.
+ *
+ * @throws Exception if the compilation fails
+ */
+ default void compile() throws Exception {
+ // Do nothing by default
+ }
/**
* Checks if the Condition is satisfied in the given {@code context}.
diff --git a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java
index 07b803cca73..15bb3ada645 100644
--- a/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java
+++ b/bundles/org.openhab.core.automation/src/main/java/org/openhab/core/automation/internal/RuleEngineImpl.java
@@ -110,6 +110,7 @@
* @author Benedikt Niehues - change behavior for unregistering ModuleHandler
* @author Markus Rathgeb - use a managed rule
* @author Ana Dimova - new reference syntax: list[index], map["key"], bean.field
+ * @author Florian Hotze - add support for script condition/action compilation
*/
@Component(immediate = true, service = { RuleManager.class })
@NonNullByDefault
@@ -819,6 +820,8 @@ public synchronized void setEnabled(String uid, boolean enable) {
*
*
Set the module handlers. If there are errors, set the rule status (handler error) and return with error
* indication.
+ *
Compile the conditions and actions. If there are errors, set the rule status (handler error) and return with
+ * indication.
*
Register the rule. Set the rule status and return with success indication.
*
*
@@ -845,6 +848,11 @@ private boolean activateRule(final WrappedRule rule) {
return false;
}
+ // Compile the conditions and actions and so check if they are valid.
+ if (!compileRule(rule)) {
+ return false;
+ }
+
// Register the rule and set idle status.
register(rule);
setStatus(ruleUID, new RuleStatusInfo(RuleStatus.IDLE));
@@ -862,6 +870,58 @@ private boolean activateRule(final WrappedRule rule) {
return true;
}
+ /**
+ * Compile the conditions and actions of the given rule.
+ * If there are errors, set the rule status (handler error) and return with indication.
+ *
+ * @param rule the rule whose conditions and actions should be compiled
+ * @return true if compilation succeeded, otherwise false
+ */
+ private boolean compileRule(final WrappedRule rule) {
+ try {
+ compileConditions(rule);
+ compileActions(rule);
+ return true;
+ } catch (Throwable t) {
+ setStatus(rule.getUID(), new RuleStatusInfo(RuleStatus.UNINITIALIZED,
+ RuleStatusDetail.HANDLER_INITIALIZING_ERROR, t.getMessage()));
+ unregister(rule);
+ return false;
+ }
+ }
+
+ /**
+ * Compile the conditions and actions of the given rule.
+ * If there are errors, set the rule status (handler error).
+ *
+ * @param ruleUID the UID of the rule whose conditions and actions should be compiled
+ */
+ private void compileRule(String ruleUID) {
+ final WrappedRule rule = getManagedRule(ruleUID);
+ if (rule == null) {
+ logger.warn("Failed to compile rule '{}': Invalid Rule UID", ruleUID);
+ return;
+ }
+ synchronized (this) {
+ final RuleStatus ruleStatus = getRuleStatus(ruleUID);
+ if (ruleStatus != null && ruleStatus != RuleStatus.IDLE) {
+ logger.error("Failed to compile rule ‘{}' with status '{}'", ruleUID, ruleStatus.name());
+ return;
+ }
+ // change state to INITIALIZING
+ setStatus(ruleUID, new RuleStatusInfo(RuleStatus.INITIALIZING));
+ }
+ if (!compileRule(rule)) {
+ return;
+ }
+ // change state to IDLE only if the rule has not been DISABLED.
+ synchronized (this) {
+ if (getRuleStatus(ruleUID) == RuleStatus.INITIALIZING) {
+ setStatus(ruleUID, new RuleStatusInfo(RuleStatus.IDLE));
+ }
+ }
+ }
+
@Override
public @Nullable RuleStatusInfo getStatusInfo(String ruleUID) {
final WrappedRule rule = managedRules.get(ruleUID);
@@ -1134,6 +1194,32 @@ private Map getContext(String ruleUID, @Nullable Set
return context;
}
+ /**
+ * This method compiles conditions of the {@link Rule} when they exist.
+ * It is called when the rule is initialized.
+ *
+ * @param rule compiled rule.
+ */
+ private void compileConditions(WrappedRule rule) {
+ final Collection conditions = rule.getConditions();
+ if (conditions.isEmpty()) {
+ return;
+ }
+ for (WrappedCondition wrappedCondition : conditions) {
+ final Condition condition = wrappedCondition.unwrap();
+ ConditionHandler cHandler = wrappedCondition.getModuleHandler();
+ if (cHandler != null) {
+ try {
+ cHandler.compile();
+ } catch (Throwable t) {
+ String errMessage = "Failed to pre-compile condition: " + condition.getId() + "(" + t.getMessage()
+ + ")";
+ throw new RuntimeException(errMessage, t);
+ }
+ }
+ }
+ }
+
/**
* This method checks if all rule's condition are satisfied or not.
*
@@ -1163,6 +1249,31 @@ private boolean calculateConditions(WrappedRule rule) {
return true;
}
+ /**
+ * This method compiles actions of the {@link Rule} when they exist.
+ * It is called when the rule is initialized.
+ *
+ * @param rule compiled rule.
+ */
+ private void compileActions(WrappedRule rule) {
+ final Collection actions = rule.getActions();
+ if (actions.isEmpty()) {
+ return;
+ }
+ for (WrappedAction wrappedAction : actions) {
+ final Action action = wrappedAction.unwrap();
+ ActionHandler aHandler = wrappedAction.getModuleHandler();
+ if (aHandler != null) {
+ try {
+ aHandler.compile();
+ } catch (Throwable t) {
+ String errMessage = "Failed to pre-compile action: " + action.getId() + "(" + t.getMessage() + ")";
+ throw new RuntimeException(errMessage, t);
+ }
+ }
+ }
+ }
+
/**
* This method evaluates actions of the {@link Rule} and set their {@link Output}s when they exist.
*
@@ -1435,7 +1546,7 @@ public String getOutputName() {
@Override
public void onReadyMarkerAdded(ReadyMarker readyMarker) {
- executeRulesWithStartLevel();
+ compileRules();
}
@Override
@@ -1443,6 +1554,21 @@ public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
started = false;
}
+ /**
+ * This method compiles the conditions and actions of all enabled rules.
+ * It is called when the rule engine is started.
+ * By compiling when the rule engine is started, we make sure all conditions and actions are compiled, even if their
+ * handlers weren't available when the rule was added to the rule engine.
+ */
+ private void compileRules() {
+ getScheduledExecutor().submit(() -> {
+ ruleRegistry.getAll().stream() //
+ .filter(r -> isEnabled(r.getUID())) //
+ .forEach(r -> compileRule(r.getUID()));
+ executeRulesWithStartLevel();
+ });
+ }
+
private void executeRulesWithStartLevel() {
getScheduledExecutor().submit(() -> {
ruleRegistry.getAll().stream() //
diff --git a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_nl.properties b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_nl.properties
index 1eda9757fed..6a057dfae78 100644
--- a/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_nl.properties
+++ b/bundles/org.openhab.core.automation/src/main/resources/OH-INF/i18n/automation_nl.properties
@@ -26,23 +26,29 @@ module-type.core.GenericCompareCondition.input.input.description = De invoer zal
module-type.core.GenericEventCondition.label = Gebeurtenis Voorwaarde
module-type.core.GenericEventCondition.description = Voorwaarde voor gebeurtenissen
-module-type.core.GenericEventCondition.config.topic.description = onderwerp moet overeenkomen
-module-type.core.GenericEventCondition.config.eventType.description = eventType moet overeenkomen
-module-type.core.GenericEventCondition.config.source.description = bron moet overeenkomen
-module-type.core.GenericEventCondition.config.payload.description = payload moet overeenkomen
+module-type.core.GenericEventCondition.config.topic.label = Onderwerp
+module-type.core.GenericEventCondition.config.topic.description = het onderwerp, als een bestandssysteem glob (*, ** en {} operators). zal overeenkomen met alle events indien leeg
+module-type.core.GenericEventCondition.config.source.label = Bron
+module-type.core.GenericEventCondition.config.source.description = de bron van het event (bijv. org.openhab.core.expire, etc.). zal overeenkomen met alle events indien leeg
+module-type.core.GenericEventCondition.config.types.label = Event Type
+module-type.core.GenericEventCondition.config.types.description = het event type waar de trigger moet naar luisteren. meerdere types kunnen, door een komma gescheiden, worden opgegeven. zal evereenkomen met alle events indien leeg
+module-type.core.GenericEventCondition.config.payload.label = Event Inhoud
+module-type.core.GenericEventCondition.config.payload.description = Een regex die overeenkomt met de geserialiseerde inhoud van het event. zal overeenkomen met alle events indien leeg
module-type.core.GenericEventCondition.input.event.label = Gebeurtenis
-module-type.core.GenericEventCondition.input.event.description = De gebeurtenissen die zijn verzonden.
+module-type.core.GenericEventCondition.input.event.description = Het verzonden event.
# core.GenericEventTrigger
module-type.core.GenericEventTrigger.label = Basis GebeurtenisTrigger
module-type.core.GenericEventTrigger.description = Triggert regels op gebeurtenissen
-module-type.core.GenericEventTrigger.config.eventTopic.label = Onderwerp
-module-type.core.GenericEventTrigger.config.eventTopic.description = Dit is het onderwerp, de trigger zal luisteren naar\: >>openhab/*<<
-module-type.core.GenericEventTrigger.config.eventSource.label = Bron
-module-type.core.GenericEventTrigger.config.eventSource.description = Dit is de bron van de gebeurtenis (bijv. itemnaam)
-module-type.core.GenericEventTrigger.config.eventTypes.label = Gebeurtenistype
-module-type.core.GenericEventTrigger.config.eventTypes.description = het gebeurtenistype waar de trigger moet naar luisteren. Meerdere types kunnen, door een komma gescheiden, worden opgegeven
+module-type.core.GenericEventTrigger.config.topic.label = Onderwerp
+module-type.core.GenericEventTrigger.config.topic.description = het onderwerp, als een bestandssysteem glob (*, ** en {} operators). zal overeenkomen met alle events indien leeg
+module-type.core.GenericEventTrigger.config.source.label = Bron
+module-type.core.GenericEventTrigger.config.source.description = de bron van het event (bijv. org.openhab.core.expire, etc.). zal overeenkomen met alle events indien leeg
+module-type.core.GenericEventTrigger.config.types.label = Event Type
+module-type.core.GenericEventTrigger.config.types.description = het event type waar de trigger moet naar luisteren. meerdere types kunnen, door een komma gescheiden, worden opgegeven. zal overeenkomen met alle events indien leeg
+module-type.core.GenericEventTrigger.config.payload.label = Event Inhoud
+module-type.core.GenericEventTrigger.config.payload.description = Een regex die overeenkomt met de geserialiseerde inhoud van het event. zal overeenkomen met alle events indien leeg
module-type.core.GenericEventTrigger.output.event.label = Gebeurtenis
module-type.core.GenericEventTrigger.output.event.description = De gebeurtenissen die zijn verzonden.
@@ -60,6 +66,8 @@ module-type.core.GroupCommandTrigger.config.command.option.OPEN = OPEN
module-type.core.GroupCommandTrigger.config.command.option.CLOSED = GESLOTEN
module-type.core.GroupCommandTrigger.config.command.option.UP = OMHOOG
module-type.core.GroupCommandTrigger.config.command.option.DOWN = OMLAAG
+module-type.core.GroupCommandTrigger.output.triggeringGroup.label = Triggering Groep
+module-type.core.GroupCommandTrigger.output.triggeringGroup.description = de item groep waartoe het item behoort
module-type.core.GroupCommandTrigger.output.triggeringItem.label = Triggering item
module-type.core.GroupCommandTrigger.output.triggeringItem.description = het lid van de groep dat het commando heeft ontvangen
module-type.core.GroupCommandTrigger.output.command.label = Commando
@@ -112,6 +120,8 @@ module-type.core.GroupStateUpdateTrigger.config.state.option.OPEN = OPEN
module-type.core.GroupStateUpdateTrigger.config.state.option.CLOSED = GESLOTEN
module-type.core.GroupStateUpdateTrigger.config.state.option.UP = OMHOOG
module-type.core.GroupStateUpdateTrigger.config.state.option.DOWN = OMLAAG
+module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.label = Triggering Groep
+module-type.core.GroupStateUpdateTrigger.output.triggeringGroup.description = de item groep waartoe het item behoort
module-type.core.GroupStateUpdateTrigger.output.triggeringItem.label = Triggering Item
module-type.core.GroupStateUpdateTrigger.output.triggeringItem.description = het lid van de groep dat zijn status deed updaten
module-type.core.GroupStateUpdateTrigger.output.state.label = Status
@@ -245,21 +255,21 @@ module-type.core.ItemStateUpdateTrigger.output.event.description = De verzonden
# core.RuleEnablementAction
-module-type.core.RuleEnablementAction.label = schakelt regels in of uit
-module-type.core.RuleEnablementAction.description = Schakelt een regel of groep van regels op basis van hun UID's in of uit.
-module-type.core.RuleEnablementAction.config.enable.label = Regels inschakelen
-module-type.core.RuleEnablementAction.config.enable.description = 'true' activeert alle opgegeven regels, 'false' deactiveert deze.
+module-type.core.RuleEnablementAction.label = scènes, scripts en rules in- of uitschakelen
+module-type.core.RuleEnablementAction.description = Schakelt één enkele of een groep van de door hun UID's gespecificeerde in of uit.
+module-type.core.RuleEnablementAction.config.enable.label = Inschakelen/Uitschakelen
+module-type.core.RuleEnablementAction.config.enable.description = 'true' activeert al de opgegeven, 'false' deactiveert deze.
module-type.core.RuleEnablementAction.config.enable.option.true = Inschakelen
module-type.core.RuleEnablementAction.config.enable.option.false = Uitschakelen
-module-type.core.RuleEnablementAction.config.ruleUIDs.label = Regels
-module-type.core.RuleEnablementAction.config.ruleUIDs.description = Specificeert de regels die ingeschakeld of uitgeschakeld moeten worden.
+module-type.core.RuleEnablementAction.config.ruleUIDs.label = Scènes, scripts & rules
+module-type.core.RuleEnablementAction.config.ruleUIDs.description = Specificeert deze die ingeschakeld of uitgeschakeld moeten worden.
# core.RunRuleAction
-module-type.core.RunRuleAction.label = regels uitvoeren
-module-type.core.RunRuleAction.description = uitvoeren van een regel of groep van regels op basis van hun UID's.
-module-type.core.RunRuleAction.config.ruleUIDs.label = Regels
-module-type.core.RunRuleAction.config.ruleUIDs.description = Specificeert de doelregel(s) die moeten worden uitgevoerd.
+module-type.core.RunRuleAction.label = uitvoeren van scènes, scripts & rules
+module-type.core.RunRuleAction.description = Voert één enkele of een groep van de door hun UID's gespecificeerde uit.
+module-type.core.RunRuleAction.config.ruleUIDs.label = Scènes, scripts & rules
+module-type.core.RunRuleAction.config.ruleUIDs.description = Geeft de scène(s), script(s) & rule(s) die moeten worden uitgevoerd.
module-type.core.RunRuleAction.config.considerConditions.label = Condities Overwegen
module-type.core.RunRuleAction.config.considerConditions.description = Geeft aan of de condities van de uit te voeren doelregel(s) moeten worden overwogen of niet.
module-type.core.RunRuleAction.config.considerConditions.option.true = Ja
@@ -382,6 +392,10 @@ module-type.timer.DateTimeTrigger.label = het is een datum en tijd opgegeven in
module-type.timer.DateTimeTrigger.description = Triggert op een tijdstip opgegeven in een item
module-type.timer.DateTimeTrigger.config.itemName.label = Item
module-type.timer.DateTimeTrigger.config.itemName.description = de naam van het item
+module-type.timer.DateTimeTrigger.config.timeOnly.label = Enkel tijdstip
+module-type.timer.DateTimeTrigger.config.timeOnly.description = Geeft aan of alleen de tijd van het item moet worden vergeleken, of de datum en tijd.
+module-type.timer.DateTimeTrigger.config.timeOnly.option.true = Ja
+module-type.timer.DateTimeTrigger.config.timeOnly.option.false = Nee
# timer.DayOfWeekCondition
diff --git a/bundles/org.openhab.core.config.core/pom.xml b/bundles/org.openhab.core.config.core/pom.xml
index 8221b4b6b68..e9ce9275f70 100644
--- a/bundles/org.openhab.core.config.core/pom.xml
+++ b/bundles/org.openhab.core.config.core/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.core
diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigParser.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigParser.java
index 9e3797fde5a..df8b60c2871 100644
--- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigParser.java
+++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/ConfigParser.java
@@ -127,6 +127,9 @@ private ConfigParser() {
try {
value = valueAs(value, type);
if (value == null) {
+ LOGGER.warn(
+ "Could not set value for field '{}' because conversion failed. Check your configuration value.",
+ fieldName);
continue;
}
LOGGER.trace("Setting value ({}) {} to field '{}' in configuration class {}", type.getSimpleName(),
diff --git a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/i18n/ConfigDescriptionI18nUtil.java b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/i18n/ConfigDescriptionI18nUtil.java
index ea72276dd42..b40cccaf4bd 100644
--- a/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/i18n/ConfigDescriptionI18nUtil.java
+++ b/bundles/org.openhab.core.config.core/src/main/java/org/openhab/core/config/core/internal/i18n/ConfigDescriptionI18nUtil.java
@@ -14,6 +14,7 @@
import java.net.URI;
import java.util.Locale;
+import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -31,6 +32,7 @@
* @author Dennis Nobel - Initial contribution
* @author Alex Tugarev - Extended for pattern and option label
* @author Thomas Höfer - Extended for unit label
+ * @author Laurent Garnier - Changed inferred key for add-ons + alternative key
*/
@NonNullByDefault
public class ConfigDescriptionI18nUtil {
@@ -38,6 +40,8 @@ public class ConfigDescriptionI18nUtil {
private final TranslationProvider i18nProvider;
private static final Pattern DELIMITER = Pattern.compile("[:=\\s]");
+ private static final Set ADDON_TYPES = Set.of("automation", "binding", "io", "misc", "persistence", "voice",
+ "ui");
public ConfigDescriptionI18nUtil(TranslationProvider i18nProvider) {
this.i18nProvider = i18nProvider;
@@ -45,23 +49,18 @@ public ConfigDescriptionI18nUtil(TranslationProvider i18nProvider) {
public @Nullable String getParameterPattern(Bundle bundle, URI configDescriptionURI, String parameterName,
@Nullable String defaultPattern, @Nullable Locale locale) {
- String key = I18nUtil.stripConstantOr(defaultPattern,
- () -> inferKey(configDescriptionURI, parameterName, "pattern"));
- return i18nProvider.getText(bundle, key, defaultPattern, locale);
+ return getParameterValue(bundle, configDescriptionURI, parameterName, "pattern", defaultPattern, locale);
}
public @Nullable String getParameterDescription(Bundle bundle, URI configDescriptionURI, String parameterName,
@Nullable String defaultDescription, @Nullable Locale locale) {
- String key = I18nUtil.stripConstantOr(defaultDescription,
- () -> inferKey(configDescriptionURI, parameterName, "description"));
- return i18nProvider.getText(bundle, key, defaultDescription, locale);
+ return getParameterValue(bundle, configDescriptionURI, parameterName, "description", defaultDescription,
+ locale);
}
public @Nullable String getParameterLabel(Bundle bundle, URI configDescriptionURI, String parameterName,
@Nullable String defaultLabel, @Nullable Locale locale) {
- String key = I18nUtil.stripConstantOr(defaultLabel,
- () -> inferKey(configDescriptionURI, parameterName, "label"));
- return i18nProvider.getText(bundle, key, defaultLabel, locale);
+ return getParameterValue(bundle, configDescriptionURI, parameterName, "label", defaultLabel, locale);
}
public @Nullable String getParameterOptionLabel(Bundle bundle, URI configDescriptionURI, String parameterName,
@@ -70,10 +69,8 @@ public ConfigDescriptionI18nUtil(TranslationProvider i18nProvider) {
return defaultOptionLabel;
}
- String key = I18nUtil.stripConstantOr(defaultOptionLabel,
- () -> inferKey(configDescriptionURI, parameterName, "option." + optionValue));
-
- return i18nProvider.getText(bundle, key, defaultOptionLabel, locale);
+ return getParameterValue(bundle, configDescriptionURI, parameterName, "option." + optionValue,
+ defaultOptionLabel, locale);
}
public @Nullable String getParameterUnitLabel(Bundle bundle, URI configDescriptionURI, String parameterName,
@@ -84,14 +81,28 @@ public ConfigDescriptionI18nUtil(TranslationProvider i18nProvider) {
return label;
}
}
- String key = I18nUtil.stripConstantOr(defaultUnitLabel,
- () -> inferKey(configDescriptionURI, parameterName, "unitLabel"));
- return i18nProvider.getText(bundle, key, defaultUnitLabel, locale);
+ return getParameterValue(bundle, configDescriptionURI, parameterName, "unitLabel", defaultUnitLabel, locale);
+ }
+
+ private @Nullable String getParameterValue(Bundle bundle, URI configDescriptionURI, String parameterName,
+ String parameterAttribute, @Nullable String defaultValue, @Nullable Locale locale) {
+ String key = I18nUtil.stripConstantOr(defaultValue,
+ () -> inferKey(true, configDescriptionURI, parameterName, parameterAttribute));
+ String value = i18nProvider.getText(bundle, key, null, locale);
+ if (value == null && ADDON_TYPES.contains(configDescriptionURI.getScheme())) {
+ key = I18nUtil.stripConstantOr(defaultValue,
+ () -> inferKey(false, configDescriptionURI, parameterName, parameterAttribute));
+ value = i18nProvider.getText(bundle, key, null, locale);
+ }
+ return value != null ? value : defaultValue;
}
- private String inferKey(URI configDescriptionURI, String parameterName, String lastSegment) {
+ private String inferKey(boolean checkAddonType, URI configDescriptionURI, String parameterName,
+ String lastSegment) {
+ String prefix = checkAddonType && ADDON_TYPES.contains(configDescriptionURI.getScheme()) ? "addon"
+ : configDescriptionURI.getScheme();
String uri = configDescriptionURI.getSchemeSpecificPart().replace(":", ".");
- return configDescriptionURI.getScheme() + ".config." + uri + "." + parameterName + "." + lastSegment;
+ return prefix + ".config." + uri + "." + parameterName + "." + lastSegment;
}
private boolean isValidPropertyKey(@Nullable String key) {
diff --git a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_nl.properties b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_nl.properties
index a9c4afc833d..2f65a12026f 100644
--- a/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_nl.properties
+++ b/bundles/org.openhab.core.config.core/src/main/resources/OH-INF/i18n/validation_nl.properties
@@ -11,3 +11,7 @@ min_value_numeric_violated=De waarde mag niet kleiner zijn dan {0}.
pattern_violated=De waarde {0} komt niet overeen met het patroon {1}.
options_violated=De waarde {0} komt niet overeen met de toegestane parameteropties. Toegestane opties zijn\: {1}
multiple_limit_violated=Slechts {0} elementen zijn toegestaan terwijl er {1} worden opgegeven.
+
+bridge_not_configured=Bridge configureren is verplicht.
+type_description_missing=Type beschrijving {0} voor {1} niet gevonden, hoewel we eerder de aanwezigheid hebben gecontroleerd.
+config_description_missing=Configuratiebeschrijving {0} voor {1} niet gevonden, hoewel we de aanwezigheid eerder hebben gecontroleerd.
diff --git a/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml b/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml
index 0c825cd2374..2e1e8e69545 100644
--- a/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.addon.ip/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.addon.ip
diff --git a/bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml b/bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml
index ab46d535b77..79adc852e09 100644
--- a/bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.addon.mdns
diff --git a/bundles/org.openhab.core.config.discovery.addon.process/pom.xml b/bundles/org.openhab.core.config.discovery.addon.process/pom.xml
index f26a5100232..93869c740b4 100644
--- a/bundles/org.openhab.core.config.discovery.addon.process/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.addon.process/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.addon.process
diff --git a/bundles/org.openhab.core.config.discovery.addon.sddp/.classpath b/bundles/org.openhab.core.config.discovery.addon.sddp/.classpath
new file mode 100644
index 00000000000..d3d6b3c11b6
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.sddp/.classpath
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.core.config.discovery.addon.sddp/.project b/bundles/org.openhab.core.config.discovery.addon.sddp/.project
new file mode 100644
index 00000000000..f86bba378d1
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.sddp/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.core.config.discovery.addon.upnp
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/bundles/org.openhab.core.config.discovery.addon.sddp/NOTICE b/bundles/org.openhab.core.config.discovery.addon.sddp/NOTICE
new file mode 100644
index 00000000000..6c17d0d8a45
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.sddp/NOTICE
@@ -0,0 +1,14 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-core
+
diff --git a/bundles/org.openhab.core.config.discovery.addon.sddp/pom.xml b/bundles/org.openhab.core.config.discovery.addon.sddp/pom.xml
new file mode 100644
index 00000000000..e18075232e1
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.sddp/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.core.bundles
+ org.openhab.core.reactor.bundles
+ 4.3.0-SNAPSHOT
+
+
+ org.openhab.core.config.discovery.addon.sddp
+
+ openHAB Core :: Bundles :: SDDP Suggested Add-on Finder
+
+
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.addon
+ ${project.version}
+
+
+ org.openhab.core.bundles
+ org.openhab.core.addon
+ ${project.version}
+
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery.sddp
+ ${project.version}
+
+
+
diff --git a/bundles/org.openhab.core.config.discovery.addon.sddp/src/main/java/org/openhab/core/config/discovery/addon/sddp/SddpAddonFinder.java b/bundles/org.openhab.core.config.discovery.addon.sddp/src/main/java/org/openhab/core/config/discovery/addon/sddp/SddpAddonFinder.java
new file mode 100644
index 00000000000..9c8448641f2
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.sddp/src/main/java/org/openhab/core/config/discovery/addon/sddp/SddpAddonFinder.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright (c) 2010-2024 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.core.config.discovery.addon.sddp;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.addon.AddonDiscoveryMethod;
+import org.openhab.core.addon.AddonInfo;
+import org.openhab.core.addon.AddonMatchProperty;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.config.discovery.addon.AddonFinder;
+import org.openhab.core.config.discovery.addon.AddonFinderConstants;
+import org.openhab.core.config.discovery.addon.BaseAddonFinder;
+import org.openhab.core.config.discovery.sddp.SddpDevice;
+import org.openhab.core.config.discovery.sddp.SddpDeviceParticipant;
+import org.openhab.core.config.discovery.sddp.SddpDiscoveryService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a {@link SddpAddonFinder} for finding suggested Addons via SDDP.
+ *
+ * It checks the binding's addon.xml 'match-property' elements for the following SDDP properties:
+ *
driver
+ *
host
+ *
ipAddress
+ *
macAddress
+ *
manufacturer
+ *
model
+ *
port
+ *
primaryProxy
+ *
proxies
+ *
type
+ *
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = AddonFinder.class, name = SddpAddonFinder.SERVICE_NAME)
+public class SddpAddonFinder extends BaseAddonFinder implements SddpDeviceParticipant {
+
+ public static final String SERVICE_TYPE = AddonFinderConstants.SERVICE_TYPE_SDDP;
+ public static final String SERVICE_NAME = AddonFinderConstants.SERVICE_NAME_SDDP;
+
+ private static final String DRIVER = "driver";
+ private static final String HOST = "host";
+ private static final String IP_ADDRESS = "ipAddress";
+ private static final String MAC_ADDRESS = "macAddress";
+ private static final String MANUFACTURER = "manufacturer";
+ private static final String MODEL = "model";
+ private static final String PORT = "port";
+ private static final String PRIMARY_PROXY = "primaryProxy";
+ private static final String PROXIES = "proxies";
+ private static final String TYPE = "type";
+
+ private static final Set SUPPORTED_PROPERTIES = Set.of(DRIVER, HOST, IP_ADDRESS, MAC_ADDRESS, MANUFACTURER,
+ MODEL, PORT, PRIMARY_PROXY, PROXIES, TYPE);
+
+ private final Logger logger = LoggerFactory.getLogger(SddpAddonFinder.class);
+ private final Set foundDevices = new HashSet<>();
+
+ private @Nullable SddpDiscoveryService sddpDiscoveryService = null;
+
+ @Activate
+ public SddpAddonFinder(
+ @Reference(service = DiscoveryService.class, target = "(protocol=sddp)") DiscoveryService discoveryService) {
+ if (discoveryService instanceof SddpDiscoveryService sddpDiscoveryService) {
+ sddpDiscoveryService.addSddpDeviceParticipant(this);
+ this.sddpDiscoveryService = sddpDiscoveryService;
+ } else {
+ logger.warn("SddpAddonFinder() DiscoveryService is not an SddpDiscoveryService");
+ }
+ }
+
+ @Deactivate
+ public void deactivate() {
+ SddpDiscoveryService sddpDiscoveryService = this.sddpDiscoveryService;
+ if (sddpDiscoveryService != null) {
+ sddpDiscoveryService.removeSddpDeviceParticipant(this);
+ this.sddpDiscoveryService = null;
+ }
+ unsetAddonCandidates();
+ foundDevices.clear();
+ }
+
+ @Override
+ public void deviceAdded(SddpDevice device) {
+ foundDevices.add(device);
+ }
+
+ @Override
+ public void deviceRemoved(SddpDevice device) {
+ foundDevices.remove(device);
+ }
+
+ @Override
+ public String getServiceName() {
+ return SERVICE_NAME;
+ }
+
+ @Override
+ public Set getSuggestedAddons() {
+ Set result = new HashSet<>();
+ for (AddonInfo candidate : addonCandidates) {
+ for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream()
+ .filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) {
+ Map matchProperties = method.getMatchProperties().stream()
+ .collect(Collectors.toMap(AddonMatchProperty::getName, AddonMatchProperty::getPattern));
+
+ Set propertyNames = new HashSet<>(matchProperties.keySet());
+ propertyNames.removeAll(SUPPORTED_PROPERTIES);
+
+ if (!propertyNames.isEmpty()) {
+ logger.warn("Add-on '{}' addon.xml file contains unsupported 'match-property' [{}]",
+ candidate.getUID(), String.join(",", propertyNames));
+ break;
+ }
+
+ logger.trace("Checking candidate: {}", candidate.getUID());
+ for (SddpDevice device : foundDevices) {
+ logger.trace("Checking device: {}", device.host);
+ if (propertyMatches(matchProperties, HOST, device.host)
+ && propertyMatches(matchProperties, IP_ADDRESS, device.ipAddress)
+ && propertyMatches(matchProperties, MAC_ADDRESS, device.macAddress)
+ && propertyMatches(matchProperties, MANUFACTURER, device.manufacturer)
+ && propertyMatches(matchProperties, MODEL, device.model)
+ && propertyMatches(matchProperties, PORT, device.port)
+ && propertyMatches(matchProperties, PRIMARY_PROXY, device.primaryProxy)
+ && propertyMatches(matchProperties, PROXIES, device.proxies)
+ && propertyMatches(matchProperties, TYPE, device.type)) {
+ result.add(candidate);
+ logger.debug("Suggested add-on found: {}", candidate.getUID());
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void setAddonCandidates(List candidates) {
+ super.setAddonCandidates(candidates);
+ }
+}
diff --git a/bundles/org.openhab.core.config.discovery.addon.sddp/src/test/java/org/openhab/core/config/discovery/addon/sddp/test/SddpAddonFinderTests.java b/bundles/org.openhab.core.config.discovery.addon.sddp/src/test/java/org/openhab/core/config/discovery/addon/sddp/test/SddpAddonFinderTests.java
new file mode 100644
index 00000000000..fff72278ce7
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.addon.sddp/src/test/java/org/openhab/core/config/discovery/addon/sddp/test/SddpAddonFinderTests.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2010-2024 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.core.config.discovery.addon.sddp.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.Test;
+import org.openhab.core.addon.AddonDiscoveryMethod;
+import org.openhab.core.addon.AddonInfo;
+import org.openhab.core.addon.AddonMatchProperty;
+import org.openhab.core.config.discovery.addon.sddp.SddpAddonFinder;
+import org.openhab.core.config.discovery.sddp.SddpDevice;
+import org.openhab.core.config.discovery.sddp.SddpDiscoveryService;
+
+/**
+ * JUnit tests for the {@link SddpAddonFinder}.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public class SddpAddonFinderTests {
+
+ private static final Map DEVICE_FIELDS = Map.of(
+ // @formatter:off
+ "From", "\"192.168.4.237:1902\"",
+ "Host", "\"JVC_PROJECTOR-E0DADC152802\"",
+ "Max-Age", "1800",
+ "Type", "\"JVCKENWOOD:Projector\"",
+ "Primary-Proxy", "\"projector\"",
+ "Proxies", "\"projector\"",
+ "Manufacturer", "\"JVCKENWOOD\"",
+ "Model", "\"DLA-RS3100_NZ8\"",
+ "Driver", "\"projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i\"");
+ // @formatter:on
+
+ private List createAddonInfos() {
+ AddonDiscoveryMethod method = new AddonDiscoveryMethod().setServiceType(SddpAddonFinder.SERVICE_TYPE)
+ .setMatchProperties(List.of(new AddonMatchProperty("host", "JVC.*")));
+ List addonInfos = new ArrayList<>();
+ addonInfos.add(AddonInfo.builder("jvc", "binding").withName("JVC").withDescription("JVC Kenwood")
+ .withDiscoveryMethods(List.of(method)).build());
+ return addonInfos;
+ }
+
+ @Test
+ public void testFinder() {
+ SddpDevice device = new SddpDevice(DEVICE_FIELDS, false);
+
+ List addonInfos = createAddonInfos();
+ SddpAddonFinder finder = new SddpAddonFinder(mock(SddpDiscoveryService.class));
+
+ finder.setAddonCandidates(addonInfos);
+
+ Set suggestions;
+ AddonInfo info;
+
+ finder.deviceAdded(device);
+ suggestions = finder.getSuggestedAddons();
+ assertFalse(suggestions.isEmpty());
+ info = suggestions.stream().findFirst().orElse(null);
+ assertNotNull(info);
+ assertEquals("JVC Kenwood", info.getDescription());
+
+ finder.deviceRemoved(device);
+ suggestions = finder.getSuggestedAddons();
+ assertTrue(suggestions.isEmpty());
+ }
+}
diff --git a/bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml b/bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml
index cffbd119de2..a195f43d7dd 100644
--- a/bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.addon.upnp/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.addon.upnp
diff --git a/bundles/org.openhab.core.config.discovery.addon.usb/pom.xml b/bundles/org.openhab.core.config.discovery.addon.usb/pom.xml
index 7ca3bcafc8d..50dae1cda13 100644
--- a/bundles/org.openhab.core.config.discovery.addon.usb/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.addon.usb/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.addon.usb
diff --git a/bundles/org.openhab.core.config.discovery.addon/pom.xml b/bundles/org.openhab.core.config.discovery.addon/pom.xml
index 2d1c396ad5c..802424e62d3 100644
--- a/bundles/org.openhab.core.config.discovery.addon/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.addon/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.addon
diff --git a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java
index 8486506a869..774f3064bf2 100644
--- a/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java
+++ b/bundles/org.openhab.core.config.discovery.addon/src/main/java/org/openhab/core/config/discovery/addon/AddonFinderConstants.java
@@ -35,6 +35,10 @@ public class AddonFinderConstants {
public static final String CFG_FINDER_MDNS = "suggestionFinderMdns";
public static final String SERVICE_NAME_MDNS = SERVICE_TYPE_MDNS + ADDON_SUGGESTION_FINDER;
+ public static final String SERVICE_TYPE_SDDP = "sddp";
+ public static final String CFG_FINDER_SDDP = "suggestionFinderSddp";
+ public static final String SERVICE_NAME_SDDP = SERVICE_TYPE_SDDP + ADDON_SUGGESTION_FINDER;
+
public static final String SERVICE_TYPE_UPNP = "upnp";
public static final String CFG_FINDER_UPNP = "suggestionFinderUpnp";
public static final String SERVICE_NAME_UPNP = SERVICE_TYPE_UPNP + ADDON_SUGGESTION_FINDER;
@@ -43,13 +47,14 @@ public class AddonFinderConstants {
public static final String CFG_FINDER_USB = "suggestionFinderUsb";
public static final String SERVICE_NAME_USB = SERVICE_TYPE_USB + ADDON_SUGGESTION_FINDER;
- public static final List SUGGESTION_FINDERS = List.of(SERVICE_NAME_IP, SERVICE_NAME_MDNS, SERVICE_NAME_UPNP,
- SERVICE_NAME_USB);
+ public static final List SUGGESTION_FINDERS = List.of(SERVICE_NAME_IP, SERVICE_NAME_MDNS, SERVICE_NAME_SDDP,
+ SERVICE_NAME_UPNP, SERVICE_NAME_USB);
public static final Map SUGGESTION_FINDER_TYPES = Map.of(SERVICE_NAME_IP, SERVICE_TYPE_IP,
- SERVICE_NAME_MDNS, SERVICE_TYPE_MDNS, SERVICE_NAME_UPNP, SERVICE_TYPE_UPNP, SERVICE_NAME_USB,
- SERVICE_TYPE_USB);
+ SERVICE_NAME_MDNS, SERVICE_TYPE_MDNS, SERVICE_NAME_SDDP, SERVICE_TYPE_SDDP, SERVICE_NAME_UPNP,
+ SERVICE_TYPE_UPNP, SERVICE_NAME_USB, SERVICE_TYPE_USB);
public static final Map SUGGESTION_FINDER_CONFIGS = Map.of(SERVICE_NAME_IP, CFG_FINDER_IP,
- SERVICE_NAME_MDNS, CFG_FINDER_MDNS, SERVICE_NAME_UPNP, CFG_FINDER_UPNP, SERVICE_NAME_USB, CFG_FINDER_USB);
+ SERVICE_NAME_MDNS, CFG_FINDER_MDNS, SERVICE_NAME_SDDP, CFG_FINDER_SDDP, SERVICE_NAME_UPNP, CFG_FINDER_UPNP,
+ SERVICE_NAME_USB, CFG_FINDER_USB);
}
diff --git a/bundles/org.openhab.core.config.discovery.mdns/pom.xml b/bundles/org.openhab.core.config.discovery.mdns/pom.xml
index f0ef9975b58..b83cea9da21 100644
--- a/bundles/org.openhab.core.config.discovery.mdns/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.mdns/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.mdns
diff --git a/bundles/org.openhab.core.config.discovery.sddp/.classpath b/bundles/org.openhab.core.config.discovery.sddp/.classpath
new file mode 100644
index 00000000000..585aba264ee
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/.classpath
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.core.config.discovery.sddp/.project b/bundles/org.openhab.core.config.discovery.sddp/.project
new file mode 100644
index 00000000000..29654a1ee13
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.core.config.discovery.upnp
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/bundles/org.openhab.core.config.discovery.sddp/NOTICE b/bundles/org.openhab.core.config.discovery.sddp/NOTICE
new file mode 100644
index 00000000000..6c17d0d8a45
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/NOTICE
@@ -0,0 +1,14 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-core
+
diff --git a/bundles/org.openhab.core.config.discovery.sddp/pom.xml b/bundles/org.openhab.core.config.discovery.sddp/pom.xml
new file mode 100644
index 00000000000..78dd7dc5b76
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/pom.xml
@@ -0,0 +1,25 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.core.bundles
+ org.openhab.core.reactor.bundles
+ 4.3.0-SNAPSHOT
+
+
+ org.openhab.core.config.discovery.sddp
+
+ openHAB Core :: Bundles :: Configuration SDDP Discovery
+
+
+
+ org.openhab.core.bundles
+ org.openhab.core.config.discovery
+ ${project.version}
+
+
+
+
diff --git a/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDevice.java b/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDevice.java
new file mode 100644
index 00000000000..881901c7b3e
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDevice.java
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2010-2024 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.core.config.discovery.sddp;
+
+import java.time.Instant;
+import java.util.Map;
+import java.util.Objects;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * A DTO class containing data from an SDDP device discovery result.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public class SddpDevice {
+
+ /**
+ * The network address of the device.
+ * For example: 192.168.4.237:1902
+ */
+ public final String from;
+
+ /**
+ * The host address of the device.
+ * For example: JVC_PROJECTOR-E0DADC152802 or JVC_PROJECTOR-E0:DA:DC:15:28:02
+ * Note: the last 12 resp. 17 characters represent the MAC address of the device.
+ */
+ public final String host;
+
+ /**
+ * The number of seconds after which the device shall no longer considered to be alive on the network.
+ * For example: 1800 (a String value).
+ */
+ public final String maxAge;
+
+ /**
+ * The type of the device. Usually a colon delimited combination of a manufacturer id and a device type id.
+ * For example: JVCKENWOOD:Projector
+ */
+ public final String type;
+
+ /**
+ * The id of the primary proxy that provides device services.
+ * For example: projector
+ */
+ public final String primaryProxy;
+
+ /**
+ * A comma delimited list of proxies.
+ * For example: projector,thingy,etc
+ * Normally the first entry is the primary proxy.
+ */
+ public final String proxies;
+
+ /**
+ * The device manufacturer.
+ * For example: JVCKENWOOD
+ */
+ public final String manufacturer;
+
+ /**
+ * The model number of the device.
+ * For example: DLA-RS3100_NZ8
+ */
+ public final String model;
+
+ /**
+ * The driver id.
+ * For example: projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i
+ */
+ public final String driver;
+
+ /**
+ * The dotted IP address part of the 'from' field.
+ * For example: 192.168.4.237
+ */
+ public final String ipAddress;
+
+ /**
+ * The port part of the 'from' field.
+ * For example: 1902 (a String value)
+ */
+ public final String port;
+
+ /**
+ * The MAC address of the device as derived from the last 12 characters of the host field.
+ * It is presented in lower-case, dash delimited, format.
+ * For example: e0-da-dc-15-28-02
+ * Therefore it may be used as a (unique) sub- part of a Thing UID.
+ */
+ public final String macAddress;
+
+ /**
+ * The instant after which the device shall be considered as having left the network.
+ */
+ public final Instant expireInstant;
+
+ /**
+ * Constructor.
+ *
+ * @param headers a map of parameter name / value pairs.
+ * @param offline indicates if the device is being created from a NOTIFY OFFLINE announcement.
+ */
+ public SddpDevice(Map headers, boolean offline) {
+ from = headers.getOrDefault("From", "").replaceAll("^\"|\"$", "");
+ host = headers.getOrDefault("Host", "").replaceAll("^\"|\"$", "");
+ maxAge = headers.getOrDefault("Max-Age", "").replaceAll("^\"|\"$", "");
+ type = headers.getOrDefault("Type", "").replaceAll("^\"|\"$", "");
+ primaryProxy = headers.getOrDefault("Primary-Proxy", "").replaceAll("^\"|\"$", "");
+ proxies = headers.getOrDefault("Proxies", "").replaceAll("^\"|\"$", "");
+ manufacturer = headers.getOrDefault("Manufacturer", "").replaceAll("^\"|\"$", "");
+ model = headers.getOrDefault("Model", "").replaceAll("^\"|\"$", "");
+ driver = headers.getOrDefault("Driver", "").replaceAll("^\"|\"$", "");
+
+ String[] fromParts = from.split(":");
+ ipAddress = fromParts[0];
+ port = fromParts.length > 1 ? fromParts[1] : "";
+
+ String[] hostParts = host.split("-|_");
+ macAddress = hostParts.length <= 1 ? ""
+ : hostParts[hostParts.length - 1].replace(":", "").replaceAll("(..)(?!$)", "$1-").toLowerCase();
+
+ expireInstant = offline ? Instant.now().minusMillis(1)
+ : Instant.now().plusSeconds(maxAge.isBlank() ? 0 : Integer.parseInt(maxAge));
+ }
+
+ /**
+ * Set uniqueness is determined by the From field only
+ */
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (obj instanceof SddpDevice other) {
+ return Objects.equals(from, other.from);
+ }
+ return false;
+ }
+
+ /**
+ * Set uniqueness is determined by the From field only
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(from);
+ }
+
+ /**
+ * Check if the creation time plus max-age instant is exceeded.
+ */
+ public boolean isExpired() {
+ return Instant.now().isAfter(expireInstant);
+ }
+}
diff --git a/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDeviceParticipant.java b/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDeviceParticipant.java
new file mode 100644
index 00000000000..e01aa7b5622
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDeviceParticipant.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2024 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.core.config.discovery.sddp;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A {@link SddpDeviceParticipant} that is registered as a service is picked up by the {@link SddpDiscoveryService} and
+ * can thus be informed when the SDDP service discovers or removes an {@link SddpDevice}.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public interface SddpDeviceParticipant {
+
+ void deviceAdded(SddpDevice device);
+
+ void deviceRemoved(SddpDevice device);
+}
diff --git a/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDiscoveryParticipant.java b/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDiscoveryParticipant.java
new file mode 100644
index 00000000000..fd11375507f
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDiscoveryParticipant.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2010-2024 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.core.config.discovery.sddp;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+
+/**
+ * A {@link SddpDiscoveryParticipant} that is registered as a service is picked up by the {@link SddpDiscoveryService}
+ * and can thus contribute {@link DiscoveryResult}s from SDDP scans.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+public interface SddpDiscoveryParticipant {
+
+ /**
+ * Defines the list of thing types that this participant can identify
+ *
+ * @return a set of thing type UIDs for which results can be created
+ */
+ Set getSupportedThingTypeUIDs();
+
+ /**
+ * Creates a discovery result for a SDDP device
+ *
+ * @param device the SDDP device found on the network
+ * @return the according discovery result or null, if device is not
+ * supported by this participant
+ */
+ @Nullable
+ DiscoveryResult createResult(SddpDevice device);
+
+ /**
+ * Returns the thing UID for a SDDP device
+ *
+ * @param device the SDDP device on the network
+ * @return a thing UID or null, if device is not supported
+ * by this participant
+ */
+ @Nullable
+ ThingUID getThingUID(SddpDevice device);
+}
diff --git a/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDiscoveryService.java b/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDiscoveryService.java
new file mode 100644
index 00000000000..476d879457a
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/src/main/java/org/openhab/core/config/discovery/sddp/SddpDiscoveryService.java
@@ -0,0 +1,439 @@
+/**
+ * Copyright (c) 2010-2024 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.core.config.discovery.sddp;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+import java.net.NetworkInterface;
+import java.net.ServerSocket;
+import java.net.SocketTimeoutException;
+import java.net.StandardSocketOptions;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.net.CidrAddress;
+import org.openhab.core.net.NetworkAddressChangeListener;
+import org.openhab.core.net.NetworkAddressService;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.ThingUID;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a {@link DiscoveryService} implementation, which can find SDDP devices in the network.
+ * Support for bindings can be achieved by implementing and registering a {@link SddpDiscoveryParticipant}.
+ * Support for finders can be achieved by implementing and registering a {@link SddpDeviceParticipant}.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+@Component(immediate = true, service = DiscoveryService.class, property = "protocol=sddp", configurationPid = "discovery.sddp")
+public class SddpDiscoveryService extends AbstractDiscoveryService
+ implements AutoCloseable, NetworkAddressChangeListener {
+
+ private static final int SDDP_PORT = 1902;
+ private static final String SDDP_IP_ADDRESS = "239.255.255.250";
+ private static final InetSocketAddress SDDP_GROUP = new InetSocketAddress(SDDP_IP_ADDRESS, SDDP_PORT);
+
+ private static final int READ_BUFFER_SIZE = 1024;
+ private static final Duration SOCKET_TIMOUT = Duration.ofMillis(1000);
+ private static final Duration SEARCH_LISTEN_DURATION = Duration.ofSeconds(5);
+ private static final Duration CACHE_PURGE_INTERVAL = Duration.ofSeconds(300);
+
+ private static final String SEARCH_REQUEST_BODY_FORMAT = "SEARCH * SDDP/1.0\r\nHost: \"%s:%d\"\r\n";
+ private static final String SEARCH_RESPONSE_HEADER = "SDDP/1.0 200 OK";
+
+ private static final String NOTIFY_ALIVE_HEADER = "NOTIFY ALIVE SDDP/1.0";
+ private static final String NOTIFY_IDENTIFY_HEADER = "NOTIFY IDENTIFY SDDP/1.0";
+ private static final String NOTIFY_OFFLINE_HEADER = "NOTIFY OFFLINE SDDP/1.0";
+
+ private final Logger logger = LoggerFactory.getLogger(SddpDiscoveryService.class);
+ private final Set foundDevicesCache = ConcurrentHashMap.newKeySet();
+ private final Set discoveryParticipants = ConcurrentHashMap.newKeySet();
+ private final Set deviceParticipants = ConcurrentHashMap.newKeySet();
+
+ private final NetworkAddressService networkAddressService;
+
+ private boolean closing = false;
+
+ private @Nullable Future> listenBackgroundMulticastTask = null;
+ private @Nullable Future> listenActiveScanUnicastTask = null;
+ private @Nullable ScheduledFuture> purgeExpiredDevicesTask = null;
+
+ @Activate
+ public SddpDiscoveryService(final @Nullable Map configProperties, //
+ final @Reference NetworkAddressService networkAddressService, //
+ final @Reference TranslationProvider i18nProvider, //
+ final @Reference LocaleProvider localeProvider) {
+ super((int) SEARCH_LISTEN_DURATION.getSeconds());
+
+ this.networkAddressService = networkAddressService;
+ this.i18nProvider = i18nProvider;
+ this.localeProvider = localeProvider;
+
+ super.activate(configProperties); // note: this starts listenBackgroundMulticastTask
+
+ purgeExpiredDevicesTask = scheduler.scheduleWithFixedDelay(() -> purgeExpiredDevices(),
+ CACHE_PURGE_INTERVAL.getSeconds(), CACHE_PURGE_INTERVAL.getSeconds(), TimeUnit.SECONDS);
+ }
+
+ @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+ public void addSddpDeviceParticipant(SddpDeviceParticipant participant) {
+ deviceParticipants.add(participant);
+ foundDevicesCache.stream().filter(d -> !d.isExpired()).forEach(d -> participant.deviceAdded(d));
+ startScan();
+ }
+
+ @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
+ protected void addSddpDiscoveryParticipant(SddpDiscoveryParticipant participant) {
+ discoveryParticipants.add(participant);
+ foundDevicesCache.stream().filter(d -> !d.isExpired()).forEach(d -> {
+ DiscoveryResult result = participant.createResult(d);
+ if (result != null) {
+ DiscoveryResult localizedResult = getLocalizedDiscoveryResult(result,
+ FrameworkUtil.getBundle(participant.getClass()));
+ thingDiscovered(localizedResult);
+ }
+ });
+ }
+
+ /**
+ * Cancel the given task.
+ */
+ private void cancelTask(@Nullable Future> task) {
+ if (task != null) {
+ task.cancel(true);
+ }
+ }
+
+ @Override
+ public void close() {
+ deactivate();
+ }
+
+ /**
+ * Optionally create an {@link SddpDevice} object from UDP packet data if the data is good.
+ */
+ public Optional createSddpDevice(String data) {
+ if (!data.isBlank()) {
+ List lines = data.lines().toList();
+ if (lines.size() > 1) {
+ String statement = lines.get(0).strip();
+ boolean offline = statement.startsWith(NOTIFY_OFFLINE_HEADER);
+ if (offline || statement.startsWith(NOTIFY_ALIVE_HEADER) || statement.startsWith(NOTIFY_IDENTIFY_HEADER)
+ || statement.startsWith(SEARCH_RESPONSE_HEADER)) {
+ Map headers = new HashMap<>();
+ for (int i = 1; i < lines.size(); i++) {
+ String[] header = lines.get(i).split(":", 2);
+ if (header.length > 1) {
+ headers.put(header[0].strip(), header[1].strip());
+ }
+ }
+ return Optional.of(new SddpDevice(headers, offline));
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ @Deactivate
+ @Override
+ protected void deactivate() {
+ closing = true;
+
+ foundDevicesCache.clear();
+ discoveryParticipants.clear();
+ deviceParticipants.clear();
+
+ super.deactivate(); // note: this cancels and nulls listenBackgroundMulticastTask
+
+ cancelTask(listenActiveScanUnicastTask);
+ listenActiveScanUnicastTask = null;
+
+ cancelTask(purgeExpiredDevicesTask);
+ purgeExpiredDevicesTask = null;
+ }
+
+ @Override
+ public Set getSupportedThingTypes() {
+ Set supportedThingTypes = new HashSet<>();
+ discoveryParticipants.forEach(p -> supportedThingTypes.addAll(p.getSupportedThingTypeUIDs()));
+ return supportedThingTypes;
+ }
+
+ /**
+ * Continue to listen for incoming SDDP multicast messages until the thread is externally interrupted.
+ */
+ private void listenBackGroundMulticast() {
+ MulticastSocket socket = null;
+ NetworkInterface networkInterface = null;
+
+ try {
+ networkInterface = NetworkInterface
+ .getByInetAddress(InetAddress.getByName(networkAddressService.getPrimaryIpv4HostAddress()));
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("listenBackGroundMulticast() starting on interface '{}'",
+ networkInterface.getDisplayName());
+ }
+
+ socket = new MulticastSocket(SDDP_PORT);
+ socket.joinGroup(SDDP_GROUP, networkInterface);
+ socket.setSoTimeout((int) SOCKET_TIMOUT.toMillis());
+
+ DatagramPacket packet = null;
+ byte[] buffer = new byte[READ_BUFFER_SIZE];
+
+ // loop listen for responses
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ if (packet == null) {
+ packet = new DatagramPacket(buffer, buffer.length);
+ }
+ socket.receive(packet);
+ processPacket(packet);
+ packet = null;
+ } catch (SocketTimeoutException e) {
+ // socket.receive() will time out every 1 second so the thread won't block
+ }
+ }
+ } catch (IOException e) {
+ if (!closing) {
+ logger.warn("listenBackGroundMulticast error '{}'", e.getMessage());
+ }
+ } finally {
+ if (socket != null && networkInterface != null) {
+ try {
+ socket.leaveGroup(SDDP_GROUP, networkInterface);
+ } catch (IOException e) {
+ if (!closing) {
+ logger.warn("listenBackGroundMulticast() error '{}'", e.getMessage());
+ }
+ }
+ socket.close();
+ }
+ }
+ }
+
+ /**
+ * Send a single outgoing SEARCH 'ping' and then continue to listen for incoming SDDP unicast responses until the
+ * loop time elapses or the thread is externally interrupted.
+ */
+ private void listenActiveScanUnicast() {
+ // get a free port number
+ int port;
+ try (ServerSocket portFinder = new ServerSocket(0)) {
+ port = portFinder.getLocalPort();
+ } catch (IOException e) {
+ logger.warn("listenActiveScanUnicast() port finder error '{}'", e.getMessage());
+ return;
+ }
+
+ try (DatagramSocket socket = new DatagramSocket(port)) {
+ String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
+ NetworkInterface networkInterface = NetworkInterface.getByInetAddress(InetAddress.getByName(ipAddress));
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("listenActiveScanUnicast() starting on '{}:{}' on interface '{}'", ipAddress, port,
+ networkInterface.getDisplayName());
+ }
+
+ socket.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface);
+ socket.setSoTimeout((int) SOCKET_TIMOUT.toMillis());
+
+ DatagramPacket packet;
+ byte[] buffer;
+
+ // send search request
+ String search = String.format(SEARCH_REQUEST_BODY_FORMAT, ipAddress, port);
+ buffer = search.getBytes(StandardCharsets.UTF_8);
+ packet = new DatagramPacket(buffer, buffer.length, new InetSocketAddress(SDDP_IP_ADDRESS, SDDP_PORT));
+ socket.send(packet);
+ logger.trace("Packet sent to '{}:{}' content:\r\n{}", SDDP_IP_ADDRESS, SDDP_PORT, search);
+
+ final Instant listenDoneTime = Instant.now().plus(SEARCH_LISTEN_DURATION);
+ buffer = new byte[READ_BUFFER_SIZE];
+ packet = null;
+
+ // loop listen for responses
+ while (Instant.now().isBefore(listenDoneTime) && !Thread.currentThread().isInterrupted()) {
+ try {
+ if (packet == null) {
+ packet = new DatagramPacket(buffer, buffer.length);
+ }
+ socket.receive(packet);
+ processPacket(packet);
+ packet = null;
+ } catch (SocketTimeoutException e) {
+ // receive will time out every 1 second so the thread won't block
+ }
+ }
+ } catch (IOException e) {
+ if (!closing) {
+ logger.warn("listenActiveScanUnicast() error '{}'", e.getMessage());
+ }
+ }
+ }
+
+ @Modified
+ @Override
+ protected void modified(@Nullable Map configProperties) {
+ super.modified(configProperties);
+ }
+
+ /**
+ * If the network interfaces change then cancel and recreate all pending tasks.
+ */
+ @Override
+ public synchronized void onChanged(List added, List removed) {
+ Future> multicastTask = listenBackgroundMulticastTask;
+ if (multicastTask != null && !multicastTask.isDone()) {
+ multicastTask.cancel(true);
+ listenBackgroundMulticastTask = scheduler.submit(() -> listenBackGroundMulticast());
+ }
+ Future> unicastTask = listenActiveScanUnicastTask;
+ if (unicastTask != null && !unicastTask.isDone()) {
+ unicastTask.cancel(true);
+ listenActiveScanUnicastTask = scheduler.submit(() -> listenActiveScanUnicast());
+ }
+ }
+
+ /**
+ * Process the {@link DatagramPacket} content by trying to create an {@link SddpDevice} and eventually adding it to
+ * the foundDevicesCache, and if so, then notifying all listeners.
+ *
+ * @param packet a datagram packet that arrived over the network.
+ */
+ private synchronized void processPacket(DatagramPacket packet) {
+ String content = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
+ if (logger.isTraceEnabled()) {
+ logger.trace("Packet received from '{}:{}' content:\r\n{}", packet.getAddress().getHostAddress(),
+ packet.getPort(), content);
+ }
+ Optional deviceOptional = createSddpDevice(content);
+ if (deviceOptional.isPresent()) {
+ SddpDevice device = deviceOptional.get();
+ foundDevicesCache.remove(device);
+
+ if (device.isExpired()) {
+ // device created from a NOTIFY OFFLINE announcement
+ discoveryParticipants.forEach(p -> {
+ DiscoveryResult discoveryResult = p.createResult(device);
+ if (discoveryResult != null) {
+ thingRemoved(discoveryResult.getThingUID());
+ }
+ });
+ deviceParticipants.forEach(f -> f.deviceRemoved(device));
+ } else {
+ // device created from a NOTIFY ALIVE announcement or SEARCH response
+ foundDevicesCache.add(device);
+ discoveryParticipants.forEach(p -> {
+ DiscoveryResult discoveryResult = p.createResult(device);
+ if (discoveryResult != null) {
+ DiscoveryResult localizedResult = getLocalizedDiscoveryResult(discoveryResult,
+ FrameworkUtil.getBundle(p.getClass()));
+ thingDiscovered(localizedResult);
+ }
+ });
+ deviceParticipants.forEach(f -> f.deviceAdded(device));
+ }
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("processPacket() foundDevices={}, deviceParticipants={}, discoveryParticipants={}",
+ foundDevicesCache.size(), deviceParticipants.size(), discoveryParticipants.size());
+ }
+ }
+ }
+
+ /**
+ * Purge expired devices and notify all listeners.
+ */
+ private synchronized void purgeExpiredDevices() {
+ Set devices = new HashSet<>(foundDevicesCache);
+ devices.stream().filter(d -> d.isExpired()).forEach(d -> {
+ discoveryParticipants.forEach(p -> {
+ ThingUID thingUID = p.getThingUID(d);
+ if (thingUID != null) {
+ thingRemoved(thingUID);
+ }
+ });
+ deviceParticipants.forEach(f -> f.deviceRemoved(d));
+ });
+ foundDevicesCache.clear();
+ foundDevicesCache.addAll(devices.stream().filter(d -> !d.isExpired()).collect(Collectors.toSet()));
+ }
+
+ public void removeSddpDeviceParticipant(SddpDeviceParticipant participant) {
+ deviceParticipants.remove(participant);
+ }
+
+ public void removeSddpDiscoveryParticipant(SddpDiscoveryParticipant participant) {
+ discoveryParticipants.remove(participant);
+ }
+
+ @Override
+ protected void startBackgroundDiscovery() {
+ Future> task = listenBackgroundMulticastTask;
+ if (task == null || task.isDone()) {
+ listenBackgroundMulticastTask = scheduler.submit(() -> listenBackGroundMulticast());
+ }
+ }
+
+ /**
+ * Schedule to send one single SDDP SEARCH request, and listen for responses.
+ */
+ @Override
+ protected void startScan() {
+ Future> task = listenActiveScanUnicastTask;
+ if (task == null || task.isDone()) {
+ listenActiveScanUnicastTask = scheduler.submit(() -> listenActiveScanUnicast());
+ }
+ }
+
+ @Override
+ protected void stopBackgroundDiscovery() {
+ cancelTask(listenBackgroundMulticastTask);
+ listenBackgroundMulticastTask = null;
+ }
+}
diff --git a/bundles/org.openhab.core.config.discovery.sddp/src/test/java/org/openhab/core/config/discovery/sddp/test/SddpDiscoveryServiceTests.java b/bundles/org.openhab.core.config.discovery.sddp/src/test/java/org/openhab/core/config/discovery/sddp/test/SddpDiscoveryServiceTests.java
new file mode 100644
index 00000000000..8f3fe3b96ec
--- /dev/null
+++ b/bundles/org.openhab.core.config.discovery.sddp/src/test/java/org/openhab/core/config/discovery/sddp/test/SddpDiscoveryServiceTests.java
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2010-2024 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.core.config.discovery.sddp.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.openhab.core.config.discovery.sddp.SddpDevice;
+import org.openhab.core.config.discovery.sddp.SddpDiscoveryService;
+import org.openhab.core.i18n.LocaleProvider;
+import org.openhab.core.i18n.TranslationProvider;
+import org.openhab.core.net.NetworkAddressService;
+
+/**
+ * JUnit tests for parsing SDDP discovery results.
+ *
+ * @author Andrew Fiddian-Green - Initial contribution
+ */
+@NonNullByDefault
+@TestInstance(Lifecycle.PER_CLASS)
+public class SddpDiscoveryServiceTests {
+
+ private static final String ALIVE_NOTIFICATION = """
+ NOTIFY ALIVE SDDP/1.0
+ From: "192.168.4.237:1902"
+ Host: "JVC_PROJECTOR-E0DADC152802"
+ Max-Age: 1800
+ Type: "JVCKENWOOD:Projector"
+ Primary-Proxy: "projector"
+ Proxies: "projector"
+ Manufacturer: "JVCKENWOOD"
+ Model: "DLA-RS3100_NZ8"
+ Driver: "projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i"
+ """;
+
+ private static final String IDENTIFY_NOTIFICATION = """
+ NOTIFY IDENTIFY SDDP/1.0
+ From: "192.168.4.237:1902"
+ Host: "JVC_PROJECTOR-E0:DA:DC:15:28:02"
+ Type: "JVCKENWOOD:Projector"
+ Primary-Proxy: "projector"
+ Proxies: "projector"
+ Manufacturer: "JVCKENWOOD"
+ Model: "DLA-RS3100_NZ8"
+ Driver: "projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i"
+ """;
+
+ private static final String BAD_HEADER = """
+ SDDP/1.0 404 NOT FOUND\r
+ From: "192.168.4.237:1902"\r
+ Host: "JVC_PROJECTOR-E0DADC152802"\r
+ Max-Age: 1800\r
+ Type: "JVCKENWOOD:Projector"\r
+ Primary-Proxy: "projector"\r
+ Proxies: "projector"\r
+ Manufacturer: "JVCKENWOOD"\r
+ Model: "DLA-RS3100_NZ8"\r
+ Driver: "projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i"\r
+ """;
+
+ private static final String BAD_PAYLOAD = """
+ SDDP/1.0 200 OK\r
+ """;
+
+ private static final String SEARCH_RESPONSE = """
+ SDDP/1.0 200 OK\r
+ From: "192.168.4.237:1902"\r
+ Host: "JVC_PROJECTOR-E0DADC152802"\r
+ Max-Age: 1800\r
+ Type: "JVCKENWOOD:Projector"\r
+ Primary-Proxy: "projector"\r
+ Proxies: "projector"\r
+ Manufacturer: "JVCKENWOOD"\r
+ Model: "DLA-RS3100_NZ8"\r
+ Driver: "projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i"\r
+ """;
+
+ private @NonNullByDefault({}) NetworkAddressService networkAddressService;
+
+ @BeforeAll
+ public void setup() {
+ networkAddressService = mock(NetworkAddressService.class);
+ when(networkAddressService.getPrimaryIpv4HostAddress()).thenReturn("192.168.1.1");
+ }
+
+ @Test
+ void testAliveNotification() throws Exception {
+ try (SddpDiscoveryService service = new SddpDiscoveryService(null, networkAddressService,
+ mock(TranslationProvider.class), mock(LocaleProvider.class))) {
+ Optional deviceOptional = service.createSddpDevice(ALIVE_NOTIFICATION);
+ assertTrue(deviceOptional.isPresent());
+ SddpDevice device = deviceOptional.orElse(null);
+ assertNotNull(device);
+ assertEquals("192.168.4.237:1902", device.from);
+ assertEquals("JVC_PROJECTOR-E0DADC152802", device.host);
+ assertEquals("1800", device.maxAge);
+ assertEquals("JVCKENWOOD:Projector", device.type);
+ assertEquals("projector", device.primaryProxy);
+ assertEquals("projector", device.proxies);
+ assertEquals("JVCKENWOOD", device.manufacturer);
+ assertEquals("DLA-RS3100_NZ8", device.model);
+ assertEquals("projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i", device.driver);
+ assertEquals("192.168.4.237", device.ipAddress);
+ assertEquals("e0-da-dc-15-28-02", device.macAddress);
+ assertEquals("1902", device.port);
+ }
+ }
+
+ @Test
+ void testIdentifyNotification() throws Exception {
+ try (SddpDiscoveryService service = new SddpDiscoveryService(null, networkAddressService,
+ mock(TranslationProvider.class), mock(LocaleProvider.class))) {
+ Optional deviceOptional = service.createSddpDevice(IDENTIFY_NOTIFICATION);
+ assertTrue(deviceOptional.isPresent());
+ SddpDevice device = deviceOptional.orElse(null);
+ assertNotNull(device);
+ assertEquals("192.168.4.237:1902", device.from);
+ assertEquals("JVC_PROJECTOR-E0:DA:DC:15:28:02", device.host);
+ assertTrue(device.maxAge.isBlank());
+ assertEquals("JVCKENWOOD:Projector", device.type);
+ assertEquals("projector", device.primaryProxy);
+ assertEquals("projector", device.proxies);
+ assertEquals("JVCKENWOOD", device.manufacturer);
+ assertEquals("DLA-RS3100_NZ8", device.model);
+ assertEquals("projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i", device.driver);
+ assertEquals("192.168.4.237", device.ipAddress);
+ assertEquals("e0-da-dc-15-28-02", device.macAddress);
+ assertEquals("1902", device.port);
+ }
+ }
+
+ @Test
+ void testBadHeader() throws Exception {
+ try (SddpDiscoveryService service = new SddpDiscoveryService(null, networkAddressService,
+ mock(TranslationProvider.class), mock(LocaleProvider.class))) {
+ Optional deviceOptional = service.createSddpDevice(BAD_HEADER);
+ assertFalse(deviceOptional.isPresent());
+ }
+ }
+
+ @Test
+ void testBadPayload() throws Exception {
+ try (SddpDiscoveryService service = new SddpDiscoveryService(null, networkAddressService,
+ mock(TranslationProvider.class), mock(LocaleProvider.class))) {
+ Optional deviceOptional = service.createSddpDevice(BAD_PAYLOAD);
+ assertFalse(deviceOptional.isPresent());
+ }
+ }
+
+ @Test
+ void testSearchResponse() throws Exception {
+ try (SddpDiscoveryService service = new SddpDiscoveryService(null, networkAddressService,
+ mock(TranslationProvider.class), mock(LocaleProvider.class))) {
+ Optional deviceOptional = service.createSddpDevice(SEARCH_RESPONSE);
+ assertTrue(deviceOptional.isPresent());
+ SddpDevice device = deviceOptional.orElse(null);
+ assertNotNull(device);
+ assertEquals("192.168.4.237:1902", device.from);
+ assertEquals("JVC_PROJECTOR-E0DADC152802", device.host);
+ assertEquals("1800", device.maxAge);
+ assertEquals("JVCKENWOOD:Projector", device.type);
+ assertEquals("projector", device.primaryProxy);
+ assertEquals("projector", device.proxies);
+ assertEquals("JVCKENWOOD", device.manufacturer);
+ assertEquals("DLA-RS3100_NZ8", device.model);
+ assertEquals("projector_JVCKENWOOD_DLA-RS3100_NZ8.c4i", device.driver);
+ assertEquals("192.168.4.237", device.ipAddress);
+ assertEquals("e0-da-dc-15-28-02", device.macAddress);
+ assertEquals("1902", device.port);
+ }
+ }
+}
diff --git a/bundles/org.openhab.core.config.discovery.upnp/pom.xml b/bundles/org.openhab.core.config.discovery.upnp/pom.xml
index f8a71aad2bb..3acb24a0162 100644
--- a/bundles/org.openhab.core.config.discovery.upnp/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.upnp/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.upnp
diff --git a/bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/pom.xml b/bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/pom.xml
index b94212ddd9d..b18af32a317 100644
--- a/bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.usbserial.linuxsysfs/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.usbserial.linuxsysfs
diff --git a/bundles/org.openhab.core.config.discovery.usbserial.ser2net/pom.xml b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/pom.xml
index 0d7b22e038a..4949e4d0193 100644
--- a/bundles/org.openhab.core.config.discovery.usbserial.ser2net/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.usbserial.ser2net/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.usbserial.ser2net
diff --git a/bundles/org.openhab.core.config.discovery.usbserial.windowsregistry/pom.xml b/bundles/org.openhab.core.config.discovery.usbserial.windowsregistry/pom.xml
index 72af12d984d..af322fead47 100644
--- a/bundles/org.openhab.core.config.discovery.usbserial.windowsregistry/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.usbserial.windowsregistry/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.usbserial.windowsregistry
diff --git a/bundles/org.openhab.core.config.discovery.usbserial/pom.xml b/bundles/org.openhab.core.config.discovery.usbserial/pom.xml
index 44a0f843484..14641463948 100644
--- a/bundles/org.openhab.core.config.discovery.usbserial/pom.xml
+++ b/bundles/org.openhab.core.config.discovery.usbserial/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery.usbserial
diff --git a/bundles/org.openhab.core.config.discovery/pom.xml b/bundles/org.openhab.core.config.discovery/pom.xml
index 17dd511021f..c73a8d6905b 100644
--- a/bundles/org.openhab.core.config.discovery/pom.xml
+++ b/bundles/org.openhab.core.config.discovery/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.discovery
diff --git a/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/AbstractThingHandlerDiscoveryService.java b/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/AbstractThingHandlerDiscoveryService.java
index 377c8e62fdf..0dd7aa12594 100644
--- a/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/AbstractThingHandlerDiscoveryService.java
+++ b/bundles/org.openhab.core.config.discovery/src/main/java/org/openhab/core/config/discovery/AbstractThingHandlerDiscoveryService.java
@@ -48,17 +48,16 @@ protected AbstractThingHandlerDiscoveryService(Class thingClazz, @Nullable Se
int timeout, boolean backgroundDiscoveryEnabledByDefault) throws IllegalArgumentException {
super(supportedThingTypes, timeout, backgroundDiscoveryEnabledByDefault);
this.thingClazz = thingClazz;
+ this.backgroundDiscoveryEnabled = backgroundDiscoveryEnabledByDefault;
}
protected AbstractThingHandlerDiscoveryService(Class thingClazz, @Nullable Set supportedThingTypes,
int timeout) throws IllegalArgumentException {
- super(supportedThingTypes, timeout);
- this.thingClazz = thingClazz;
+ this(thingClazz, supportedThingTypes, timeout, true);
}
protected AbstractThingHandlerDiscoveryService(Class thingClazz, int timeout) throws IllegalArgumentException {
- super(timeout);
- this.thingClazz = thingClazz;
+ this(thingClazz, null, timeout);
}
@Override
@@ -86,7 +85,8 @@ public void activate(@Nullable Map config) {
// thing handler is set. This is correctly handled in initialize
if (config != null) {
backgroundDiscoveryEnabled = ConfigParser.valueAsOrElse(
- config.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY), Boolean.class, false);
+ config.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY), Boolean.class,
+ backgroundDiscoveryEnabled);
}
}
@@ -94,7 +94,8 @@ public void activate(@Nullable Map config) {
public void modified(@Nullable Map config) {
if (config != null) {
boolean enabled = ConfigParser.valueAsOrElse(
- config.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY), Boolean.class, false);
+ config.get(DiscoveryService.CONFIG_PROPERTY_BACKGROUND_DISCOVERY), Boolean.class,
+ backgroundDiscoveryEnabled);
if (backgroundDiscoveryEnabled && !enabled) {
stopBackgroundDiscovery();
diff --git a/bundles/org.openhab.core.config.dispatch/pom.xml b/bundles/org.openhab.core.config.dispatch/pom.xml
index fe0a6858260..0718c11530e 100644
--- a/bundles/org.openhab.core.config.dispatch/pom.xml
+++ b/bundles/org.openhab.core.config.dispatch/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.dispatch
diff --git a/bundles/org.openhab.core.config.jupnp/pom.xml b/bundles/org.openhab.core.config.jupnp/pom.xml
index 8205180a3be..6059f7f8e61 100644
--- a/bundles/org.openhab.core.config.jupnp/pom.xml
+++ b/bundles/org.openhab.core.config.jupnp/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.jupnp
diff --git a/bundles/org.openhab.core.config.serial/pom.xml b/bundles/org.openhab.core.config.serial/pom.xml
index e800f84315c..ea2b499ef81 100644
--- a/bundles/org.openhab.core.config.serial/pom.xml
+++ b/bundles/org.openhab.core.config.serial/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.config.serial
diff --git a/bundles/org.openhab.core.ephemeris/pom.xml b/bundles/org.openhab.core.ephemeris/pom.xml
index 73c992077e8..cb635def7a3 100644
--- a/bundles/org.openhab.core.ephemeris/pom.xml
+++ b/bundles/org.openhab.core.ephemeris/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.ephemeris
diff --git a/bundles/org.openhab.core.id/pom.xml b/bundles/org.openhab.core.id/pom.xml
index 71ed95058d5..10cd07c605d 100644
--- a/bundles/org.openhab.core.id/pom.xml
+++ b/bundles/org.openhab.core.id/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.id
diff --git a/bundles/org.openhab.core.io.bin2json/pom.xml b/bundles/org.openhab.core.io.bin2json/pom.xml
index e9aa5bed2af..4d052c572b6 100644
--- a/bundles/org.openhab.core.io.bin2json/pom.xml
+++ b/bundles/org.openhab.core.io.bin2json/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.io.bin2json
diff --git a/bundles/org.openhab.core.io.console.eclipse/pom.xml b/bundles/org.openhab.core.io.console.eclipse/pom.xml
index 2c63ed25a92..eb75c58b7c5 100644
--- a/bundles/org.openhab.core.io.console.eclipse/pom.xml
+++ b/bundles/org.openhab.core.io.console.eclipse/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.io.console.eclipse
diff --git a/bundles/org.openhab.core.io.console.karaf/pom.xml b/bundles/org.openhab.core.io.console.karaf/pom.xml
index 7815528ad6f..243d1a41f7c 100644
--- a/bundles/org.openhab.core.io.console.karaf/pom.xml
+++ b/bundles/org.openhab.core.io.console.karaf/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.io.console.karaf
diff --git a/bundles/org.openhab.core.io.console.rfc147/pom.xml b/bundles/org.openhab.core.io.console.rfc147/pom.xml
index 40a82490646..2d428faf3c2 100644
--- a/bundles/org.openhab.core.io.console.rfc147/pom.xml
+++ b/bundles/org.openhab.core.io.console.rfc147/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.io.console.rfc147
diff --git a/bundles/org.openhab.core.io.console/pom.xml b/bundles/org.openhab.core.io.console/pom.xml
index bb92813b12d..2d8fee35ac5 100644
--- a/bundles/org.openhab.core.io.console/pom.xml
+++ b/bundles/org.openhab.core.io.console/pom.xml
@@ -7,7 +7,7 @@
org.openhab.core.bundlesorg.openhab.core.reactor.bundles
- 4.2.0-SNAPSHOT
+ 4.3.0-SNAPSHOTorg.openhab.core.io.console
diff --git a/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/AddonConsoleCommandExtension.java b/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/AddonConsoleCommandExtension.java
index 8e04601c2b8..1f9c9b501cb 100644
--- a/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/AddonConsoleCommandExtension.java
+++ b/bundles/org.openhab.core.io.console/src/main/java/org/openhab/core/io/console/internal/extension/AddonConsoleCommandExtension.java
@@ -29,6 +29,7 @@
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
/**
* Console command extension to manage add-ons
@@ -63,7 +64,7 @@ public AddonConsoleCommandExtension() {
super("addons", "Manage add-ons.");
}
- @Reference(cardinality = ReferenceCardinality.MULTIPLE)
+ @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
public void bindAddonService(AddonService addonService) {
addonServices.put(addonService.getId(), addonService);
}
diff --git a/bundles/org.openhab.core.io.http.auth/pages/authorize.html b/bundles/org.openhab.core.io.http.auth/pages/authorize.html
index 691d50ad038..e4b26dc1386 100644
--- a/bundles/org.openhab.core.io.http.auth/pages/authorize.html
+++ b/bundles/org.openhab.core.io.http.auth/pages/authorize.html
@@ -10,7 +10,7 @@
-
+