diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/.gitignore b/bundles/org.openhab.binding.mqtt.homeassistant/.gitignore
new file mode 100644
index 00000000000..11bcd382c29
--- /dev/null
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/.gitignore
@@ -0,0 +1 @@
+/src/main/python/**/__pycache__/
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/DEVELOPMENT.md b/bundles/org.openhab.binding.mqtt.homeassistant/DEVELOPMENT.md
new file mode 100644
index 00000000000..b73513dfca7
--- /dev/null
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/DEVELOPMENT.md
@@ -0,0 +1,12 @@
+src/main/python is forked from [Home Assistant core](https://github.com/home-assistant/core), in order to have near-perfect compatibility with for the Jinja templates.
+It was forked from the dev branch as of 2025-04-23, corresponding to the 2025.4.3 release of Home Assistant.
+
+The following alterations have been made:
+- Code not specifically used by this binding has been stripped out.
+- Generics and some type checks have been removed, being incompatible with GraalPy 24.2.0, which roughly corresponds with Python 3.11.
+- The standard json library is used, instead of orjson, since orjson requires a Rust compiler and would pre-compile native extensions for the architecture of the build environment, and embed them in the JAR, thus making it incompatible with other runtime architectures.
+ AFAICT this should still be fully compatible, since Home Assistant explicitly sets multiple options in order to disable features that are orjson specific.
+- ciso8601 is not included, since it has a native extension. Instead, the stdlib parser is used.
+- All asynchronous processing has been removed; the Java side threading model dominates.
+- The `hass` variable has been removed from templates; Limited templates (which are what MQTT integrations use) set it to `None` anyway.
+- Limited and strict template options have been removed; it's assumed that templates are limited and not strict.
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/NOTICE b/bundles/org.openhab.binding.mqtt.homeassistant/NOTICE
index 38d625e3492..54479cdbefd 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/NOTICE
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/NOTICE
@@ -11,3 +11,10 @@ https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
+
+== Third-party Content
+
+Parts of this code (src/main/python/) have been forked.
+* License: Apache License 2.0
+* Project: https://www.home-assistant.io/
+* Source: https://github.com/home-assistant/core
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/bnd.bnd b/bundles/org.openhab.binding.mqtt.homeassistant/bnd.bnd
new file mode 100644
index 00000000000..9b692df1603
--- /dev/null
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/bnd.bnd
@@ -0,0 +1,21 @@
+Bundle-SymbolicName: ${project.artifactId}
+DynamicImport-Package: *
+Import-Package: org.openhab.core.automation.module.script,org.openhab.core.items,org.openhab.core.library.types,javax.management,javax.script,javax.xml.datatype,javax.xml.stream;version="[1.0,2)",org.osgi.framework;version="[1.8,2)",org.slf4j;version="[1.7,2)"
+Require-Capability:
+ osgi.extender:=
+ filter:="(osgi.extender=osgi.serviceloader.processor)",
+ osgi.serviceloader:=
+ filter:="(osgi.serviceloader=org.graalvm.polyglot.impl.AbstractPolyglotImpl)";
+ cardinality:=multiple
+Require-Bundle: org.graalvm.sdk.collections;bundle-version="24.2.0",\
+ org.graalvm.sdk.jniutils;bundle-version="24.2.0",\
+ org.graalvm.sdk.nativeimage;bundle-version="24.2.0",\
+ org.graalvm.sdk.word;bundle-version="24.2.0",\
+ org.graalvm.shadowed.icu4j;bundle-version="24.2.0",\
+ org.graalvm.truffle.truffle-compiler;bundle-version="24.2.0",\
+ org.graalvm.truffle.truffle-runtime;bundle-version="24.2.0"
+
+SPI-Provider: *
+SPI-Consumer: *
+
+-fixupmessages "Classes found in the wrong directory"; restrict:=error; is:=warning
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/noEmbedDependencies.profile b/bundles/org.openhab.binding.mqtt.homeassistant/noEmbedDependencies.profile
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/pom.xml b/bundles/org.openhab.binding.mqtt.homeassistant/pom.xml
index df22ab61e78..62b4a2a281a 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/pom.xml
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/pom.xml
@@ -14,6 +14,16 @@
openHAB Add-ons :: Bundles :: MQTT HomeAssistant Convention
+
+ !sun.misc.*,
+ !sun.reflect.*,
+ !com.sun.management.*,
+ !jdk.internal.reflect.*,
+ !jdk.vm.ci.services
+
+ 24.2.0
+
+
org.openhab.addons.bundles
@@ -27,42 +37,170 @@
${project.version}
provided
-
- com.google.guava
- guava
- 33.3.1-jre
- test
-
+
- org.openhab.osgiify
- com.hubspot.jinjava.jinjava
- 2.7.4
- compile
+ org.graalvm.polyglot
+ polyglot
+ ${graalpy.version}
+
+
+
+ org.graalvm.regex
+ regex
+ ${graalpy.version}
+
+
+
+ org.graalvm.polyglot
+ python-community
+ ${graalpy.version}
+ pom
- org.openhab.osgiify
- com.google.re2j.re2j
- 1.2
- compile
-
-
- ch.obermuhlner
- big-math
- 2.3.2
- compile
-
-
- com.fasterxml.jackson.datatype
- jackson-datatype-jdk8
- ${jackson.version}
- compile
-
-
- org.openhab.osgiify
- com.hubspot.immutables.immutables-exceptions
- 1.9
- compile
+ org.graalvm.python
+ python-embedding
+ ${graalpy.version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ embed-dependencies
+
+ unpack-dependencies
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.2
+
+ -noverify
+
+
+
+ maven-resources-plugin
+ 3.3.1
+
+
+ copy-homeassistant-python
+
+ copy-resources
+
+ generate-resources
+
+ ${project.build.directory}/classes/GRAALPY-VFS/${project.groupId}/${project.artifactId}/src
+
+
+ src/main/python
+
+
+
+
+
+
+
+ org.graalvm.python
+ graalpy-maven-plugin
+ ${graalpy.version}
+
+
+ install-python-packages
+
+ process-graalpy-resources
+
+ generate-resources
+
+ GRAALPY-VFS/${project.groupId}/${project.artifactId}
+
+ awesomeversion==24.6.0
+ Jinja2==3.1.6
+ python-slugify==8.0.4
+
+
+
+
+
+ generate-python-filelist
+
+ process-graalpy-resources
+
+ process-resources
+
+ GRAALPY-VFS/${project.groupId}/${project.artifactId}
+
+ awesomeversion==24.6.0
+ Jinja2==3.1.6
+ python-slugify==8.0.4
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+
+ compile-python
+
+ exec
+
+ generate-resources
+
+ ${project.build.directory}/classes/GRAALPY-VFS/${project.groupId}/${project.artifactId}/venv/bin/python3
+
+ -m
+ compileall
+ ${project.build.directory}/classes/GRAALPY-VFS/${project.groupId}/${project.artifactId}/src
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.6.0
+
+
+
+ shade
+
+ package
+
+
+
+ org.graalvm.llvm:llvm-api
+ org.graalvm.polyglot:polyglot
+ org.graalvm.python:python-language
+ org.graalvm.python:python-resources
+ org.graalvm.regex:regex
+ org.graalvm.tools:profiler-tool
+ org.graalvm.truffle:truffle-api
+ org.graalvm.truffle:truffle-nfi
+ org.graalvm.truffle:truffle-nfi-libffi
+
+
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/feature/feature.xml b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/feature/feature.xml
index 59a216a0bd9..df50619a45c 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/feature/feature.xml
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/feature/feature.xml
@@ -6,11 +6,14 @@
openhab-runtime-base
openhab-transport-mqtt
openhab.tp-commons-net
- mvn:org.openhab.osgiify/com.hubspot.jinjava.jinjava/2.7.4
- mvn:org.openhab.osgiify/com.google.re2j.re2j/1.2
- mvn:ch.obermuhlner/big-math/2.3.2
- mvn:com.fasterxml.jackson.datatype/jackson-datatype-jdk8/${jackson.version}
- mvn:org.openhab.osgiify/com.hubspot.immutables.immutables-exceptions/1.9
+ mvn:org.openhab.osgiify/org.graalvm.sdk.collections/24.2.0
+ mvn:org.openhab.osgiify/org.graalvm.sdk.jniutils/24.2.0
+ mvn:org.openhab.osgiify/org.graalvm.sdk.nativeimage/24.2.0
+ mvn:org.openhab.osgiify/org.graalvm.sdk.word/24.2.0
+ mvn:org.openhab.osgiify/org.graalvm.shadowed.icu4j/24.2.0
+ mvn:org.openhab.osgiify/org.graalvm.shadowed.xz/24.2.0
+ mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-compiler/24.2.0
+ mvn:org.openhab.osgiify/org.graalvm.truffle.truffle-runtime/24.2.0
mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt/${project.version}
mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt.generic/${project.version}
mvn:org.openhab.addons.bundles/org.openhab.binding.mqtt.homeassistant/${project.version}
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java
index 808f272c3a5..f7604d0172a 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/generic/internal/MqttThingHandlerFactory.java
@@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mqtt.generic.MqttChannelStateDescriptionProvider;
import org.openhab.binding.mqtt.generic.MqttChannelTypeProvider;
-import org.openhab.binding.mqtt.homeassistant.internal.HomeAssistantJinjaFunctionLibrary;
+import org.openhab.binding.mqtt.homeassistant.internal.HomeAssistantPythonBridge;
import org.openhab.binding.mqtt.homeassistant.internal.HomeAssistantStateDescriptionProvider;
import org.openhab.binding.mqtt.homeassistant.internal.handler.HomeAssistantThingHandler;
import org.openhab.core.i18n.UnitProvider;
@@ -34,8 +34,6 @@ import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
-import com.hubspot.jinjava.Jinjava;
-
/**
* The {@link MqttThingHandlerFactory} is responsible for creating things and thing
* handlers.
@@ -48,8 +46,8 @@ public class MqttThingHandlerFactory extends BaseThingHandlerFactory {
private final MqttChannelTypeProvider typeProvider;
private final MqttChannelStateDescriptionProvider stateDescriptionProvider;
private final ChannelTypeRegistry channelTypeRegistry;
- private final Jinjava jinjava = new Jinjava();
private final UnitProvider unitProvider;
+ private final HomeAssistantPythonBridge python;
private static final Set SUPPORTED_THING_TYPES_UIDS = Stream
.of(MqttBindingConstants.HOMEASSISTANT_MQTT_THING).collect(Collectors.toSet());
@@ -62,8 +60,7 @@ public class MqttThingHandlerFactory extends BaseThingHandlerFactory {
this.stateDescriptionProvider = stateDescriptionProvider;
this.channelTypeRegistry = channelTypeRegistry;
this.unitProvider = unitProvider;
-
- HomeAssistantJinjaFunctionLibrary.register(jinjava.getGlobalContext());
+ this.python = new HomeAssistantPythonBridge();
}
@Override
@@ -82,12 +79,12 @@ public class MqttThingHandlerFactory extends BaseThingHandlerFactory {
if (supportsThingType(thingTypeUID)) {
return new HomeAssistantThingHandler(thing, this, typeProvider, stateDescriptionProvider,
- channelTypeRegistry, jinjava, unitProvider, 10000, 2000);
+ channelTypeRegistry, python, unitProvider, 10000, 2000);
}
return null;
}
- public Jinjava getJinjava() {
- return jinjava;
+ public HomeAssistantPythonBridge getPython() {
+ return python;
}
}
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java
index 24a9c2529e9..cbec34753d9 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/ComponentChannel.java
@@ -136,6 +136,7 @@ public class ComponentChannel {
private @Nullable String stateTopic;
private @Nullable String commandTopic;
+ private boolean parseCommandValueAsInteger;
private boolean retain;
private boolean trigger;
private boolean isAdvanced;
@@ -206,6 +207,11 @@ public class ComponentChannel {
return this;
}
+ public Builder parseCommandValueAsInteger(boolean parseCommandValueAsInteger) {
+ this.parseCommandValueAsInteger = parseCommandValueAsInteger;
+ return this;
+ }
+
public Builder trigger(boolean trigger) {
this.trigger = trigger;
return this;
@@ -265,13 +271,13 @@ public class ComponentChannel {
String localTemplateIn = templateIn;
if (localTemplateIn != null) {
- incomingTransformation = new HomeAssistantChannelTransformation(component.getJinjava(), component,
- localTemplateIn);
+ incomingTransformation = new HomeAssistantChannelTransformation(component.getPython(), component,
+ localTemplateIn, false);
}
String localTemplateOut = templateOut;
if (localTemplateOut != null) {
- outgoingTransformation = new HomeAssistantChannelTransformation(component.getJinjava(), component,
- localTemplateOut);
+ outgoingTransformation = new HomeAssistantChannelTransformation(component.getPython(), component,
+ localTemplateOut, true, parseCommandValueAsInteger);
}
channelState = new HomeAssistantChannelState(channelConfigBuilder.build(), channelUID, valueState,
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java
index 9663e2d2243..3c187f3e7f2 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/DiscoverComponents.java
@@ -38,7 +38,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
-import com.hubspot.jinjava.Jinjava;
/**
* Responsible for subscribing to the HomeAssistant MQTT components wildcard topic, either
@@ -57,7 +56,7 @@ public class DiscoverComponents implements MqttMessageSubscriber {
protected final CompletableFuture<@Nullable Void> discoverFinishedFuture = new CompletableFuture<>();
private final Gson gson;
- private final Jinjava jinjava;
+ private final HomeAssistantPythonBridge python;
private final UnitProvider unitProvider;
private @Nullable ScheduledFuture> stopDiscoveryFuture;
@@ -84,13 +83,13 @@ public class DiscoverComponents implements MqttMessageSubscriber {
*/
public DiscoverComponents(ThingUID thingUID, ScheduledExecutorService scheduler,
ChannelStateUpdateListener channelStateUpdateListener, HomeAssistantChannelLinkageChecker linkageChecker,
- AvailabilityTracker tracker, Gson gson, Jinjava jinjava, UnitProvider unitProvider) {
+ AvailabilityTracker tracker, Gson gson, HomeAssistantPythonBridge python, UnitProvider unitProvider) {
this.thingUID = thingUID;
this.scheduler = scheduler;
this.updateListener = channelStateUpdateListener;
this.linkageChecker = linkageChecker;
this.gson = gson;
- this.jinjava = jinjava;
+ this.python = python;
this.unitProvider = unitProvider;
this.tracker = tracker;
}
@@ -108,7 +107,7 @@ public class DiscoverComponents implements MqttMessageSubscriber {
if (config.length() > 0) {
try {
component = ComponentFactory.createComponent(thingUID, haID, config, updateListener, linkageChecker,
- tracker, scheduler, gson, jinjava, unitProvider);
+ tracker, scheduler, gson, python, unitProvider);
component.setConfigSeen();
logger.trace("Found HomeAssistant component {}", haID);
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantChannelTransformation.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantChannelTransformation.java
index f29b05622b2..484df982c3b 100644
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantChannelTransformation.java
+++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantChannelTransformation.java
@@ -12,137 +12,109 @@
*/
package org.openhab.binding.mqtt.homeassistant.internal;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.graalvm.polyglot.PolyglotException;
+import org.graalvm.polyglot.Value;
import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent;
import org.openhab.core.thing.binding.generic.ChannelTransformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.hubspot.jinjava.Jinjava;
-import com.hubspot.jinjava.interpret.FatalTemplateErrorsException;
-import com.hubspot.jinjava.interpret.InvalidInputException;
-import com.hubspot.jinjava.interpret.JinjavaInterpreter;
-
/**
* Provides a channel transformation for a Home Assistant channel with a
* Jinja2 template, providing the additional context and extensions required by Home Assistant
- * Based in part on the JinjaTransformationService
*
* @author Cody Cutrer - Initial contribution
*/
@NonNullByDefault
public class HomeAssistantChannelTransformation extends ChannelTransformation {
- public static class UndefinedException extends InvalidInputException {
- public UndefinedException(JinjavaInterpreter interpreter) {
- super(interpreter, "is_defined", "Value is undefined");
- }
- }
+ // These map to PayloadSentinen.NONE and PayloadSentinel.DEFAULT in mqtt/models.py
+ // NONE is used to indicate that errors should be ignored, and if any happen the original
+ // payload should be returned directly
+ public static final String PAYLOAD_SENTINEL_NONE = "none";
+ public static final String PAYLOAD_SENTINEL_DEFAULT = "default";
private final Logger logger = LoggerFactory.getLogger(HomeAssistantChannelTransformation.class);
- private final Jinjava jinjava;
- private final AbstractComponent> component;
- private final String template;
- private final ObjectMapper objectMapper = new ObjectMapper();
+ private final HomeAssistantPythonBridge python;
+ private final AbstractComponent component;
+ private final Value template;
+ private final boolean command;
+ private final String defaultValue;
+ private final boolean parseValueAsInteger;
- public HomeAssistantChannelTransformation(Jinjava jinjava, AbstractComponent> component, String template) {
+ public HomeAssistantChannelTransformation(HomeAssistantPythonBridge python, AbstractComponent component,
+ String template, boolean command) {
+ this(python, component, template, command, PAYLOAD_SENTINEL_NONE, false);
+ }
+
+ public HomeAssistantChannelTransformation(HomeAssistantPythonBridge python, AbstractComponent component,
+ String template, boolean command, boolean parseValueAsInteger) {
+ this(python, component, template, command, PAYLOAD_SENTINEL_NONE, parseValueAsInteger);
+ }
+
+ public HomeAssistantChannelTransformation(HomeAssistantPythonBridge python, AbstractComponent component,
+ String template, String defaultValue) {
+ this(python, component, template, false, defaultValue, false);
+ }
+
+ private HomeAssistantChannelTransformation(HomeAssistantPythonBridge python, AbstractComponent component,
+ String template, boolean command, String defaultValue, boolean parseValueAsInteger) {
super((String) null);
- this.jinjava = jinjava;
+ this.python = python;
this.component = component;
- this.template = template;
+ this.command = command;
+ this.template = command ? python.newCommandTemplate(template) : python.newValueTemplate(template);
+ this.defaultValue = defaultValue;
+ this.parseValueAsInteger = parseValueAsInteger;
}
@Override
public boolean isEmpty() {
- return template.isEmpty();
+ return false;
}
@Override
public Optional apply(String value) {
- return apply(template, value);
- }
-
- public Optional apply(String template, String value) {
- Map bindings = new HashMap<>();
-
- logger.debug("about to transform '{}' by the function '{}'", value, template);
-
- bindings.put("value", value);
-
- try {
- JsonNode tree = objectMapper.readTree(value);
- bindings.put("value_json", toObject(tree));
- } catch (IOException e) {
- // ok, then value_json is null...
- }
-
- return apply(template, bindings);
- }
-
- public Optional apply(String template, Map bindings) {
- String transformationResult;
-
- try {
- transformationResult = jinjava.render(template, bindings);
- } catch (FatalTemplateErrorsException e) {
- var error = e.getErrors().iterator();
- Exception exception = null;
- if (error.hasNext()) {
- exception = error.next().getException();
- }
- if (exception instanceof UndefinedException) {
- // They used the is_defined filter; it's expected to return null, with no warning
+ Object objValue = value;
+ if (parseValueAsInteger) {
+ try {
+ objValue = (int) Float.parseFloat(value);
+ } catch (NumberFormatException e) {
+ logger.warn("Failed to parse value {} as integer: {}", value, e.getMessage());
return Optional.empty();
}
- logger.warn("Applying template {} for component {} failed: {} ({})", template,
- component.getHaID().toShortTopic(), e.getMessage(), e.getClass());
+ }
+ Object result = transform(objValue);
+ if (result == null) {
return Optional.empty();
}
-
- logger.debug("transformation resulted in '{}'", transformationResult);
-
- return Optional.of(transformationResult);
+ return Optional.of(result.toString());
}
- private static @Nullable Object toObject(JsonNode node) {
- switch (node.getNodeType()) {
- case ARRAY: {
- List<@Nullable Object> result = new ArrayList<>();
- for (JsonNode el : node) {
- result.add(toObject(el));
- }
- return result;
- }
- case NUMBER:
- return node.decimalValue();
- case OBJECT: {
- Map result = new HashMap<>();
- Iterator> it = node.fields();
- while (it.hasNext()) {
- Entry field = it.next();
- result.put(field.getKey(), toObject(field.getValue()));
- }
- return result;
- }
- case STRING:
- return node.asText();
- case BOOLEAN:
- return node.asBoolean();
- case NULL:
- default:
- return null;
+ public @Nullable String transform(Object value) {
+ try {
+ return command ? python.renderCommandTemplate(template, value)
+ : python.renderValueTemplate(template, value, defaultValue);
+ } catch (PolyglotException e) {
+ logger.warn("Applying template for component {} failed: {}", component.getHaID().toShortTopic(),
+ e.getMessage(), e);
+ return null;
+ }
+ }
+
+ public @Nullable String transform(Object value, Map variables) {
+ try {
+ return command ? python.renderCommandTemplate(template, value, variables)
+ : python.renderValueTemplate(template, value, defaultValue, variables);
+ } catch (PolyglotException e) {
+ logger.warn("Applying template for component {} failed: {}", component.getHaID().toShortTopic(),
+ e.getMessage(), e);
+ return null;
}
}
}
diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantJinjaFunctionLibrary.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantJinjaFunctionLibrary.java
deleted file mode 100644
index de4a822a22c..00000000000
--- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/HomeAssistantJinjaFunctionLibrary.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (c) 2010-2025 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.mqtt.homeassistant.internal;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-
-import com.google.re2j.Matcher;
-import com.google.re2j.Pattern;
-import com.google.re2j.PatternSyntaxException;
-import com.hubspot.jinjava.interpret.Context;
-import com.hubspot.jinjava.interpret.InterpretException;
-import com.hubspot.jinjava.interpret.InvalidArgumentException;
-import com.hubspot.jinjava.interpret.InvalidReason;
-import com.hubspot.jinjava.interpret.JinjavaInterpreter;
-import com.hubspot.jinjava.interpret.TemplateSyntaxException;
-import com.hubspot.jinjava.lib.filter.Filter;
-import com.hubspot.jinjava.lib.fn.ELFunctionDefinition;
-import com.hubspot.jinjava.util.ObjectTruthValue;
-
-/**
- * Contains extensions methods exposed in Jinja transformations
- *
- * @author Cody Cutrer - Initial contribution
- */
-@NonNullByDefault
-public class HomeAssistantJinjaFunctionLibrary {
- public static void register(Context context) {
- context.registerFunction(
- new ELFunctionDefinition("", "iif", Functions.class, "iif", Object.class, Object[].class));
- context.registerFilter(new SimpleFilter("iif", Functions.class, "iif", Object.class, Object[].class));
- context.registerFilter(new IsDefinedFilter());
- context.registerFilter(new RegexFindAllFilter());
- context.registerFilter(new RegexFindAllIndexFilter());
- }
-
- @NonNullByDefault({})
- private static class SimpleFilter implements Filter {
- private final String name;
- private final Method method;
- private final Class> klass;
-
- public SimpleFilter(String name, Class> klass, String methodName, Class>... args) {
- this.name = name;
- this.klass = klass;
- try {
- this.method = klass.getDeclaredMethod(methodName, args);
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public Object filter(Object var, JinjavaInterpreter interpreter, Object[] args, Map kwargs) {
- Object[] allArgs = Stream.of(Arrays.stream(args), kwargs.values().stream()).flatMap(s -> s)
- .toArray(Object[]::new);
-
- try {
- return method.invoke(klass, var, allArgs);
- } catch (IllegalAccessException e) {
- // Not possible
- return null;
- } catch (InvocationTargetException e) {
- throw new InterpretException(e.getMessage(), e, interpreter.getLineNumber(), interpreter.getPosition());
- }
- }
-
- @Override
- public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
- // Object[] allArgs = Stream.concat(List.of(var).stream(), Arrays.stream(args)).toArray(Object[]::new);
-
- try {
- return method.invoke(klass, var, args);
- } catch (IllegalAccessException e) {
- // Not possible
- return null;
- } catch (InvocationTargetException e) {
- throw new InterpretException(e.getMessage(), e, interpreter.getLineNumber(), interpreter.getPosition());
- }
- }
- }
-
- // https://www.home-assistant.io/docs/configuration/templating/#is-defined
- @NonNullByDefault({})
- private static class IsDefinedFilter implements Filter {
- @Override
- public String getName() {
- return "is_defined";
- }
-
- @Override
- public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
- if (var == null) {
- throw new HomeAssistantChannelTransformation.UndefinedException(interpreter);
- }
-
- return var;
- }
- }
-
- // https://www.home-assistant.io/docs/configuration/templating/#regular-expressions
- // https://github.com/home-assistant/core/blob/2024.12.2/homeassistant/helpers/template.py#L2453
- @NonNullByDefault({})
- private static class RegexFindAllFilter implements Filter {
- @Override
- public String getName() {
- return "regex_findall";
- }
-
- @Override
- public Object filter(Object var, JinjavaInterpreter interpreter, String... args) {
- if (args.length > 2) {
- throw new TemplateSyntaxException(interpreter, getName(),
- "requires at most 2 arguments (regex string, ignore case)");
- }
-
- String find = null;
- if (args.length >= 1) {
- find = args[0];
- }
- String ignoreCase = null;
- if (args.length == 2) {
- ignoreCase = args[1];
- }
-
- Matcher m = regexFindAll(var, interpreter, find, ignoreCase);
-
- List