diff --git a/ros2rag.base/build.gradle b/ros2rag.base/build.gradle index afb6ee8d3a48638ddfae7a382219ec9cd3077ce4..7208baf967ee9a805e1a6582faeff527be28db64 100644 --- a/ros2rag.base/build.gradle +++ b/ros2rag.base/build.gradle @@ -20,6 +20,7 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8' implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8' implementation 'com.github.spullara.mustache.java:compiler:0.9.6' + implementation group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '2.11.2' // api 'org.jastadd:jastadd:2.3.4' runtime 'org.jastadd:jastadd:2.3.4' api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' diff --git a/ros2rag.base/src/main/jastadd/MustacheNodes.relast b/ros2rag.base/src/main/jastadd/MustacheNodes.relast index d9181d0ca3d72c6b9597f2300bb3f10b0d2fa866..69d4280f9d9038c838e83767c6e72be9a8d6d5d8 100644 --- a/ros2rag.base/src/main/jastadd/MustacheNodes.relast +++ b/ros2rag.base/src/main/jastadd/MustacheNodes.relast @@ -1,7 +1,7 @@ //TypeComponentMustache ; //rel TypeComponentMustache.TypeComponent -> TypeComponent ; -MRos2Rag ::= ReadDefinition:MReadDefinition* WriteDefinition:MWriteDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeChildren:MTypeComponent* TokenComponent:MTokenComponent*; +MRos2Rag ::= ReadDefinition:MReadDefinition* WriteDefinition:MWriteDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeComponent:MTypeComponent* TokenComponent:MTokenComponent*; abstract MUpdateDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*; MReadDefinition : MUpdateDefinition; MWriteDefinition : MUpdateDefinition; diff --git a/ros2rag.base/src/main/jastadd/backend/Generation.jadd b/ros2rag.base/src/main/jastadd/backend/Generation.jadd index 01cbb9a39e92ea4a68875d6c46a219be29ff340c..7aa915a421b4034ffa7f20406b22a041a0868bb9 100644 --- a/ros2rag.base/src/main/jastadd/backend/Generation.jadd +++ b/ros2rag.base/src/main/jastadd/backend/Generation.jadd @@ -27,6 +27,14 @@ aspect GenerationUtils { aspect AttributesForMustache { // --- MRos2Rag --- + eq MRos2Rag.getChild().mqttUpdaterAttribute() = mqttUpdaterAttribute(); + eq MRos2Rag.getChild().mqttUpdaterField() = mqttUpdaterField(); + + syn String MRos2Rag.mqttUpdaterAttribute() = getRos2Rag().mqttUpdaterAttribute(); + syn String MRos2Rag.mqttUpdaterField() = getRos2Rag().mqttUpdaterField(); + syn String MRos2Rag.mqttSetHostMethod() = getRos2Rag().mqttSetHostMethod(); + syn String MRos2Rag.mqttWaitUntilReadyMethod() = getRos2Rag().mqttWaitUntilReadyMethod(); + syn String MRos2Rag.mqttCloseMethod() = getRos2Rag().mqttCloseMethod(); // --- MUpdateDefinition --- syn String MUpdateDefinition.preemptiveExpectedValue(); @@ -34,12 +42,12 @@ aspect AttributesForMustache { syn TokenUpdateDefinition MUpdateDefinition.updateDef(); syn String MUpdateDefinition.firstInputVarName(); - eq MUpdateDefinition.getInnerMappingDefinition(int i).isLast() = i == getNumInnerMappingDefinition(); + eq MUpdateDefinition.getInnerMappingDefinition(int i).isLast() = i == getNumInnerMappingDefinition() - 1; eq MUpdateDefinition.getInnerMappingDefinition().resultVarPrefix() = resultVarPrefix(); eq MUpdateDefinition.getInnerMappingDefinition(int i).inputVarName() = i == 0 ? firstInputVarName() : resultVarPrefix() + getInnerMappingDefinition(i - 1).getMappingDefinition().methodName(); - eq MRos2Rag.getChild().mqttUpdaterField() = getRos2Rag().mqttUpdaterField(); - syn String MUpdateDefinition.mqttUpdaterAttribute() = updateDef().mqttUpdaterAttribute(); + inh String MUpdateDefinition.mqttUpdaterAttribute(); + syn String MUpdateDefinition.connectMethod() = updateDef().connectMethod(); syn TokenComponent MUpdateDefinition.token() = updateDef().getToken(); syn boolean MUpdateDefinition.alwaysApply() = updateDef().getAlwaysApply(); @@ -47,7 +55,7 @@ aspect AttributesForMustache { syn String MUpdateDefinition.parentTypeName() = token().containingTypeDecl().getName(); syn String MUpdateDefinition.tokenName() = token().getName(); syn MInnerMappingDefinition MUpdateDefinition.lastDefinition() = getInnerMappingDefinition(getNumInnerMappingDefinition() - 1); - syn String MUpdateDefinition.lastDefinitionType() = lastDefinition().ToType(); + syn String MUpdateDefinition.lastDefinitionToType() = lastDefinition().ToType(); syn String MUpdateDefinition.lastDefinitionName() = lastDefinition().methodName(); syn String MUpdateDefinition.lastResult() = resultVarPrefix() + lastDefinitionName(); syn String MUpdateDefinition.condition() { @@ -133,11 +141,13 @@ aspect AttributesForMustache { result.addDependencyDefinition(def.toMustache()); } for (TokenComponent token : getProgram().allTokenComponents()) { - result.addTokenComponent(token.toMustache()); + if (!token.getDependencySourceDefinitionList().isEmpty()) { + result.addTokenComponent(token.toMustache()); + } } for (Component child : rootNode.getComponentList()) { if (child.isTypeComponent()) { - result.addRootTypeChildren(child.asTypeComponent().toMustache()); + result.addRootTypeComponent(child.asTypeComponent().toMustache()); } } return result; @@ -220,242 +230,37 @@ aspect AspectGeneration { inh String DependencyDefinition.mqttUpdaterAttribute(); eq Ros2Rag.getChild().mqttUpdaterAttribute() = mqttUpdaterAttribute(); - // --- mqttUpdaterField --- - // --- rootNodeName --- syn String ASTNode.rootNodeName() = rootNode.getName(); - // mustache specific nodes -// syn nta TypeComponentMustache TypeComponent.mustache() { -// TypeComponentMustache result = new TypeComponentMustache(); -// result.setTypeComponent(this); -// return result; -// } -// syn String TypeComponentMustache.name() = getTypeComponent().getName(); -// inh String TypeComponentMustache.mqttUpdaterAttribute(); -// inh String TypeComponentMustache.mqttUpdaterField(); -// -// // mustache specific attributes -// syn java.util.List<TypeComponentMustache> Ros2Rag.rootTypeChildren() { -// java.util.List<TypeComponentMustache> result = new java.util.ArrayList<>(); -// for (Component child : rootNode.getComponentList()) { -// if (child.isTypeComponent()){ -// result.add(child.asTypeComponent().mustache()); -// } -// } -// return result; -// } - public String Ros2Rag.generateAspect(String rootNodeName) { - StringBuilder sb = new StringBuilder(); rootNode = getProgram().resolveTypeDecl(rootNodeName); - com.github.mustachejava.MustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory(); + return toMustache().generateAspect(); + } + + public String MRos2Rag.generateAspect() { + StringBuilder sb = new StringBuilder(); + com.github.mustachejava.reflect.ReflectionObjectHandler roh = new com.github.mustachejava.reflect.ReflectionObjectHandler() { + @Override + public com.github.mustachejava.Binding createBinding(String name, final com.github.mustachejava.TemplateContext tc, com.github.mustachejava.Code code) { + return new com.github.mustachejava.reflect.GuardedBinding(this, name, tc, code) { + @Override + protected synchronized com.github.mustachejava.util.Wrapper getWrapper(String name, java.util.List<Object> scopes) { + com.github.mustachejava.util.Wrapper wrapper = super.getWrapper(name, scopes); + if (wrapper instanceof com.github.mustachejava.reflect.MissingWrapper) { + throw new com.github.mustachejava.MustacheException(name + " not found in " + tc); + } + return wrapper; + } + }; + } + }; + com.github.mustachejava.DefaultMustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory(); + mf.setObjectHandler(roh); com.github.mustachejava.Mustache m = mf.compile("ros2rag.mustache"); m.execute(new java.io.PrintWriter(new org.jastadd.ros2rag.compiler.AppendableWriter(sb)), this); -// generateMqttAspect(sb); -// generateGrammarExtension(sb); return sb.toString(); } -// -// public void Ros2Rag.generateMqttAspect(StringBuilder sb) { -// String rootNodeName = rootNode.getName(); -// com.github.mustachejava.MustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory(); -// com.github.mustachejava.Mustache m = mf.compile("mqtt.mustache"); -// m.execute(new java.io.PrintWriter(new org.jastadd.ros2rag.compiler.AppendableWriter(sb)), this); -// } -// -// public void Ros2Rag.generateGrammarExtension(StringBuilder sb) { -// sb.append("aspect ROS2RAG {\n"); -// -// for (UpdateDefinition def : getUpdateDefinitionList()) { -// def.generateAspect(sb); -// } -// for (MappingDefinition def : allMappingDefinitions()) { -// def.generateAspect(sb); -// } -// for (DependencyDefinition def : getDependencyDefinitionList()) { -// def.generateAspect(sb); -// } -// for (TokenComponent token : getProgram().allTokenComponents()) { -// token.generateAspect(sb); -// } -// -// sb.append("}\n"); -// } -// -// abstract void UpdateDefinition.generateAspect(StringBuilder sb); -// -// String TokenUpdateDefinition.generateMappingApplication(StringBuilder sb, int indent, -// String initialInputVariableName) { -// final String resultVariablePrefix = "result"; // we do not need "_" here, because methodName begins with one -// String inputVariableName = initialInputVariableName; -// // last variable need to be declared before begin of try -// MappingDefinition lastDefinition = effectiveMappings().get(effectiveMappings().size() - 1); -// sb.append(ind(indent)).append(lastDefinition.getToType().prettyPrint()).append(" ") -// .append(resultVariablePrefix).append(lastDefinition.methodName()).append(";\n"); -// sb.append(ind(indent)).append("try {\n"); -// for (MappingDefinition mappingDefinition : effectiveMappings()) { -// String resultVariableName = resultVariablePrefix + mappingDefinition.methodName(); -// sb.append(ind(indent + 1)); -// if (mappingDefinition != lastDefinition) { -// sb.append(mappingDefinition.getToType().prettyPrint()).append(" "); -// } -// sb.append(resultVariablePrefix).append(mappingDefinition.methodName()) -// .append(" = ").append(mappingDefinition.methodName()).append("(") -// .append(inputVariableName).append(");\n"); -// inputVariableName = resultVariableName; -// } -// sb.append(ind(indent)).append("} catch (Exception e) {\n"); -// sb.append(ind(indent + 1)).append("e.printStackTrace();\n"); -// sb.append(ind(indent + 1)).append(preemptiveReturnStatement()).append("\n"); -// sb.append(ind(indent)).append("}\n"); -// if (!getAlwaysApply()) { -// MappingDefinition lastMapping = effectiveMappings().get(effectiveMappings().size() - 1); -// sb.append(ind(indent)).append("if ("); -// if (lastMapping.getToType().isArray()) { -// sb.append("java.util.Arrays.equals(").append(preemptiveExpectedValue()) -// .append(", ").append(inputVariableName).append(")"); -// } else { -// sb.append(preemptiveExpectedValue()); -// if (getToken().isPrimitiveType() && lastMapping.getToType().isPrimitiveType()) { -// sb.append(" == ").append(inputVariableName); -// } else if (lastMapping.isDefaultMappingDefinition()) { -// sb.append(" != null && ").append(preemptiveExpectedValue()).append(".equals(") -// .append(inputVariableName).append(")"); -// } else { -// sb.append(" != null ? ").append(preemptiveExpectedValue()).append(".equals(") -// .append(inputVariableName).append(")").append(" : ") -// .append(inputVariableName).append(" == null"); -// } -// } -// sb.append(") { ").append(preemptiveReturnStatement()).append(" }\n"); -// } -// return inputVariableName; -// } -// -// syn String TokenUpdateDefinition.preemptiveExpectedValue(); -// eq ReadFromMqttDefinition.preemptiveExpectedValue() = "get" + getToken().getName() + "()"; -// eq WriteToMqttDefinition.preemptiveExpectedValue() = lastValue(); -// -// syn String TokenUpdateDefinition.preemptiveReturnStatement(); -// eq ReadFromMqttDefinition.preemptiveReturnStatement() = "return;"; -// eq WriteToMqttDefinition.preemptiveReturnStatement() = "return false;"; -// -// @Override -// void ReadFromMqttDefinition.generateAspect(StringBuilder sb) { -// sb.append(ind(1)).append("public void ").append(getToken().containingTypeDecl().getName()).append(".") -// .append(connectMethod()).append("(String topic) {\n"); -// sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().newConnection(topic, message -> {\n"); -// String lastResult = generateMappingApplication(sb, 3, "message"); -// if (loggingEnabledForReads) { -// sb.append(ind(3)).append("System.out.println(\"[Read] \" + topic + \" -> ") -// .append(getToken().getName()).append(" = \" + ").append(lastResult) -// .append(");\n"); -// } -// sb.append(ind(3)).append("set").append(getToken().getName()).append("(").append(lastResult).append(");\n"); -// sb.append(ind(2)).append("});\n"); -// sb.append(ind(1)).append("}\n\n"); -// } -// -// @Override -// void WriteToMqttDefinition.generateAspect(StringBuilder sb) { -// String parentTypeName = getToken().containingTypeDecl().getName(); -// // fields -// sb.append(ind(1)).append("private String ").append(parentTypeName).append(".") -// .append(writeTopic()).append(" = null;\n"); -// sb.append(ind(1)).append("private byte[] ").append(parentTypeName).append(".") -// .append(lastValue()).append(" = null;\n"); -// -// // connect method -// sb.append(ind(1)).append("public void ").append(parentTypeName).append(".") -// .append(connectMethod()).append("(String topic, boolean writeCurrentValue) {\n"); -// sb.append(ind(2)).append(writeTopic()).append(" = topic;\n"); -// sb.append(ind(2)).append(updateMethod()).append("();\n"); -// sb.append(ind(2)).append("if (writeCurrentValue) {\n"); -// sb.append(ind(3)).append(writeMethod()).append("();\n"); -// sb.append(ind(2)).append("}\n"); -// sb.append(ind(1)).append("}\n\n"); -// -// // update method -// sb.append(ind(1)).append("protected boolean ").append(parentTypeName).append(".") -// .append(updateMethod()).append("() {\n"); -// sb.append(ind(2)).append(tokenResetMethod()).append("();\n"); -// String lastResult = generateMappingApplication(sb, 2, "get" + getToken().getName() + "()"); -// sb.append(ind(2)).append(lastValue()).append(" = ").append(lastResult).append(";\n"); -// sb.append(ind(2)).append("return true;\n"); -// sb.append(ind(1)).append("}\n\n"); -// -// // write method -// sb.append(ind(1)).append("protected void ").append(parentTypeName).append(".") -// .append(writeMethod()).append("() {\n"); -// if (loggingEnabledForWrites) { -// sb.append(ind(2)).append("System.out.println(\"[Write] ").append(getToken().getName()) -// .append(" = \" + ") -// .append("get").append(getToken().getName()).append("() + \" -> \" + ") -// .append(writeTopic()).append(");\n"); -// } -// // _mqttUpdater().publish(${writeTopic()}, ${lastValue()}); -// sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().publish(") -// .append(writeTopic()).append(", ").append(lastValue()).append(");\n"); -// sb.append(ind(1)).append("}\n\n"); -// } -// -// void MappingDefinition.generateAspect(StringBuilder sb) { -// sb.append(ind(1)).append("protected static ").append(getToType().prettyPrint()) -// .append(" ASTNode.").append(methodName()).append("(") -// .append(getFromType().prettyPrint()).append(" ").append(getFromVariableName()) -// .append(") throws Exception {\n"); -// for (String line : getContent().split("\n")) { -// if (!line.trim().isEmpty()) { -// sb.append(ind(2)).append(line).append("\n"); -// } -// } -// sb.append(ind(1)).append("}\n\n"); -// } -// -// void DependencyDefinition.generateAspect(StringBuilder sb) { -// String targetParentTypeName = getTarget().containingTypeDecl().getName(); -// String sourceParentTypeName = getSource().containingTypeDecl().getName(); -// -// // dependency method -// sb.append(ind(1)).append("public void ").append(targetParentTypeName).append(".") -// .append(dependencyMethod()).append("(").append(sourceParentTypeName).append(" source) {\n"); -// sb.append(ind(2)).append("add").append(internalRelationPrefix()).append("Source(source);\n"); -// sb.append(ind(1)).append("}\n\n"); -// } -// -// void TokenComponent.generateAspect(StringBuilder sb) { -// if (getDependencySourceDefinitionList().isEmpty()) { return; } -// -// String parentTypeName = containingTypeDecl().getName(); -// // virtual setter -// sb.append(ind(1)).append("public ").append(parentTypeName).append(" ") -// .append(parentTypeName).append(".set").append(getName()).append("(") -// .append(getJavaTypeUse().prettyPrint()).append(" value) {\n"); -// sb.append(ind(2)).append("set").append(internalName()).append("(value);\n"); -// -// for (DependencyDefinition dependencyDefinition : getDependencySourceDefinitionList()) { -// String targetParentTypeName = dependencyDefinition.getTarget().containingTypeDecl().getName(); -// sb.append(ind(2)).append("for (").append(targetParentTypeName).append(" target : get") -// .append(dependencyDefinition.internalRelationPrefix()).append("TargetList()) {\n"); -// sb.append(ind(3)).append("if (target.") -// .append(dependencyDefinition.targetUpdateDefinition().updateMethod()) -// .append("()) {\n"); -// sb.append(ind(4)).append("target.") -// .append(dependencyDefinition.targetUpdateDefinition().writeMethod()) -// .append("();\n"); -// sb.append(ind(3)).append("}\n"); -// sb.append(ind(2)).append("}\n"); -// } -// sb.append(ind(2)).append("return this;\n"); -// sb.append(ind(1)).append("}\n\n"); -// -// // virtual getter -// sb.append(ind(1)).append("public ").append(getJavaTypeUse().prettyPrint()) -// .append(" ").append(parentTypeName).append(".get").append(getName()).append("() {\n"); -// sb.append(ind(2)).append("return get").append(internalName()).append("();\n"); -// sb.append(ind(1)).append("}\n\n"); -// } } aspect RelationGeneration { diff --git a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java index ee575b9495f53eca9925c6828ba96225734fe31a..8428bf27961070afa6410a349b3412e7bff729e6 100644 --- a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java +++ b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java @@ -37,6 +37,8 @@ public class Compiler { } public void run(String[] args) throws CommandLineException, CompilerException { + System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); + System.setProperty("mustache.debug", "true"); options = new ArrayList<>(); addOptions(); commandLine = new CommandLine(options); diff --git a/ros2rag.base/src/main/resources/mappingApplication.mustache b/ros2rag.base/src/main/resources/mappingApplication.mustache index 61020400b11622dd09069118c50e6b42c166dcaf..95ce2fe29329d87f880497b37697d50c8c0687be 100644 --- a/ros2rag.base/src/main/resources/mappingApplication.mustache +++ b/ros2rag.base/src/main/resources/mappingApplication.mustache @@ -1,14 +1,14 @@ {{lastDefinitionToType}} {{resultVarPrefix}}{{lastDefinitionName}}; try { {{#InnerMappingDefinitions}} - {{^isLast}}{{ToType}} {{/isLast}}{{resultVarPrefix}}{{methodName}} = {{methodName}}({{inputVarName}});{{!inputVarName has to be computed beforehand}} + {{^last}}{{ToType}} {{/last}}{{resultVarPrefix}}{{methodName}} = {{methodName}}({{inputVarName}});{{!inputVarName has to be computed beforehand}} {{/InnerMappingDefinitions}} } catch (Exception e) { e.printStackTrace(); {{preemptiveReturn}} } {{^alwaysApply}} -if ({{condition}}) { +if ({{{condition}}}) { {{preemptiveReturn}} } {{/alwaysApply}} diff --git a/ros2rag.base/src/main/resources/mappingDefinition.mustache b/ros2rag.base/src/main/resources/mappingDefinition.mustache index 5cc1580a724bc75a15bbdf896974b7dcaa8c926a..920c5a610b0ce0cff1687122c97b6e18ce63bf7f 100644 --- a/ros2rag.base/src/main/resources/mappingDefinition.mustache +++ b/ros2rag.base/src/main/resources/mappingDefinition.mustache @@ -1,3 +1,3 @@ protected static {{toType}} ASTNode.{{methodName}}({{fromType}} {{fromVariableName}}) throws Exception { - {{content}}{{!maybe print line by line to get better indentation}} + {{{content}}}{{!maybe print line by line to get better indentation}} } diff --git a/ros2rag.base/src/main/resources/mqtt.mustache b/ros2rag.base/src/main/resources/mqtt.mustache index 3386b2ee72e5648ad8c1e4a1cadb524eca38f97d..2474e16525f6058540c3d73191369f4d2d056c4f 100644 --- a/ros2rag.base/src/main/resources/mqtt.mustache +++ b/ros2rag.base/src/main/resources/mqtt.mustache @@ -16,7 +16,7 @@ aspect MQTT { } inh MqttUpdater ASTNode.{{mqttUpdaterAttribute}}(); - {{#RootTypeChildren}} + {{#getRootTypeComponents}} eq {{rootNodeName}}.get{{name}}().{{mqttUpdaterAttribute}}() = {{mqttUpdaterField}}; - {{/RootTypeChildren}} + {{/getRootTypeComponents}} } diff --git a/ros2rag.base/src/main/resources/tokenComponent.mustache b/ros2rag.base/src/main/resources/tokenComponent.mustache index 1e58287748affc8405e4daf47ff1b54d3f19f12e..a3615c4526e5725bdd49121b16a87a011562f0cd 100644 --- a/ros2rag.base/src/main/resources/tokenComponent.mustache +++ b/ros2rag.base/src/main/resources/tokenComponent.mustache @@ -1,7 +1,7 @@ public {{parentTypeName}} {{parentTypeName}}.set{{name}}({{javaType}} value) { set{{internalName}}(value); {{#DependencyDefinitions}} - for ({{targetParentType}} target : get{{internalRelationPrefix}}TargetList) { + for ({{targetParentTypeName}} target : get{{internalRelationPrefix}}TargetList()) { {{#targetUpdateDefinition}} if (target.{{updateMethod}}()) { target.{{writeMethod}}(); diff --git a/ros2rag.base/src/main/resources/writeDefinition.mustache b/ros2rag.base/src/main/resources/writeDefinition.mustache index 81ed5f55403e2340a97b79c451fa1f841c85b1f7..3d8aab888ed0a39131f5cf00b20cfa2e9ade9048 100644 --- a/ros2rag.base/src/main/resources/writeDefinition.mustache +++ b/ros2rag.base/src/main/resources/writeDefinition.mustache @@ -18,7 +18,7 @@ protected void {{parentTypeName}}.{{writeMethod}}() { {{#loggingEnabledForWrites}} - System.out.println("[Write] {{tokenName}} = " + get{{tokenName}}() + " -> {{writeTopic}}); + System.out.println("[Write] {{tokenName}} = " + get{{tokenName}}() + " -> " + {{writeTopic}}); {{/loggingEnabledForWrites}} - {{mqttUpdateAttribute}}().publish({{writeTopic}}, {{lastValue}}); + {{mqttUpdaterAttribute}}().publish({{writeTopic}}, {{lastValue}}); }