added custom expressions for math node
This commit is contained in:
		
							parent
							
								
									34d7547607
								
							
						
					
					
						commit
						c87b14847c
					
				@ -18,8 +18,6 @@ package org.thingsboard.server.service.cf.ctx.state;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import net.objecthunter.exp4j.Expression;
 | 
			
		||||
import net.objecthunter.exp4j.ExpressionBuilder;
 | 
			
		||||
import net.objecthunter.exp4j.function.Function;
 | 
			
		||||
import net.objecthunter.exp4j.function.Functions;
 | 
			
		||||
import org.mvel2.MVEL;
 | 
			
		||||
import org.thingsboard.script.api.tbel.TbelInvokeService;
 | 
			
		||||
import org.thingsboard.server.common.data.AttributeScope;
 | 
			
		||||
@ -46,6 +44,8 @@ import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import static org.thingsboard.common.util.ExpressionFunctionsUtil.userDefinedFunctions;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class CalculatedFieldCtx {
 | 
			
		||||
 | 
			
		||||
@ -282,50 +282,4 @@ public class CalculatedFieldCtx {
 | 
			
		||||
        return "Failed to init CF state. State size exceeds limit of " + (maxStateSize / 1024) + "Kb!";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final List<Function> userDefinedFunctions = List.of(
 | 
			
		||||
            new Function("ln") {
 | 
			
		||||
                @Override
 | 
			
		||||
                public double apply(double... args) {
 | 
			
		||||
                    return Math.log(args[0]);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            new Function("lg") {
 | 
			
		||||
                @Override
 | 
			
		||||
                public double apply(double... args) {
 | 
			
		||||
                    return Math.log10(args[0]);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            new Function("logab", 2) {
 | 
			
		||||
                @Override
 | 
			
		||||
                public double apply(double... args) {
 | 
			
		||||
                    return Math.log(args[1]) / Math.log(args[0]);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            // Manually listing built-in functions to avoid inefficient equals-based lookup in Functions.getBuiltinFunction during each evaluation.
 | 
			
		||||
            Functions.getBuiltinFunction("sin"),
 | 
			
		||||
            Functions.getBuiltinFunction("cos"),
 | 
			
		||||
            Functions.getBuiltinFunction("tan"),
 | 
			
		||||
            Functions.getBuiltinFunction("cot"),
 | 
			
		||||
            Functions.getBuiltinFunction("log"),
 | 
			
		||||
            Functions.getBuiltinFunction("log2"),
 | 
			
		||||
            Functions.getBuiltinFunction("log10"),
 | 
			
		||||
            Functions.getBuiltinFunction("log1p"),
 | 
			
		||||
            Functions.getBuiltinFunction("abs"),
 | 
			
		||||
            Functions.getBuiltinFunction("acos"),
 | 
			
		||||
            Functions.getBuiltinFunction("asin"),
 | 
			
		||||
            Functions.getBuiltinFunction("atan"),
 | 
			
		||||
            Functions.getBuiltinFunction("cbrt"),
 | 
			
		||||
            Functions.getBuiltinFunction("floor"),
 | 
			
		||||
            Functions.getBuiltinFunction("sinh"),
 | 
			
		||||
            Functions.getBuiltinFunction("sqrt"),
 | 
			
		||||
            Functions.getBuiltinFunction("tanh"),
 | 
			
		||||
            Functions.getBuiltinFunction("cosh"),
 | 
			
		||||
            Functions.getBuiltinFunction("ceil"),
 | 
			
		||||
            Functions.getBuiltinFunction("pow"),
 | 
			
		||||
            Functions.getBuiltinFunction("exp"),
 | 
			
		||||
            Functions.getBuiltinFunction("expm1"),
 | 
			
		||||
            Functions.getBuiltinFunction("signum")
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -111,6 +111,11 @@
 | 
			
		||||
            <groupId>org.locationtech.jts</groupId>
 | 
			
		||||
            <artifactId>jts-core</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>net.objecthunter</groupId>
 | 
			
		||||
            <artifactId>exp4j</artifactId>
 | 
			
		||||
            <version>${exp4j.version}</version>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
    <build>
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,78 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.common.util;
 | 
			
		||||
 | 
			
		||||
import net.objecthunter.exp4j.function.Function;
 | 
			
		||||
import net.objecthunter.exp4j.function.Functions;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class ExpressionFunctionsUtil {
 | 
			
		||||
 | 
			
		||||
    public static final List<Function> userDefinedFunctions = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        userDefinedFunctions.add(
 | 
			
		||||
                new Function("ln") {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public double apply(double... args) {
 | 
			
		||||
                        return Math.log(args[0]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        );
 | 
			
		||||
        userDefinedFunctions.add(
 | 
			
		||||
                new Function("lg") {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public double apply(double... args) {
 | 
			
		||||
                        return Math.log10(args[0]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        );
 | 
			
		||||
        userDefinedFunctions.add(
 | 
			
		||||
                new Function("logab", 2) {
 | 
			
		||||
                    @Override
 | 
			
		||||
                    public double apply(double... args) {
 | 
			
		||||
                        return Math.log(args[1]) / Math.log(args[0]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        );
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("sin"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("cos"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("tan"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("cot"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("log"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("log2"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("log10"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("log1p"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("abs"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("acos"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("asin"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("atan"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("cbrt"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("floor"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("sinh"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("sqrt"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("tanh"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("cosh"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("ceil"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("pow"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("exp"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("expm1"));
 | 
			
		||||
        userDefinedFunctions.add(Functions.getBuiltinFunction("signum"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -53,6 +53,7 @@ import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import static org.thingsboard.common.util.ExpressionFunctionsUtil.userDefinedFunctions;
 | 
			
		||||
import static org.thingsboard.rule.engine.math.TbMathArgumentType.CONSTANT;
 | 
			
		||||
 | 
			
		||||
@SuppressWarnings("UnstableApiUsage")
 | 
			
		||||
@ -310,6 +311,7 @@ public class TbMathNode implements TbNode {
 | 
			
		||||
                var expr = customExpression.get();
 | 
			
		||||
                if (expr == null) {
 | 
			
		||||
                    expr = new ExpressionBuilder(config.getCustomFunction())
 | 
			
		||||
                            .functions(userDefinedFunctions)
 | 
			
		||||
                            .implicitMultiplication(true)
 | 
			
		||||
                            .variables(config.getArguments().stream().map(TbMathArgument::getName).collect(Collectors.toSet()))
 | 
			
		||||
                            .build();
 | 
			
		||||
 | 
			
		||||
@ -56,6 +56,8 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
 | 
			
		||||
import org.thingsboard.server.dao.attributes.AttributesService;
 | 
			
		||||
import org.thingsboard.server.dao.timeseries.TimeseriesService;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
import java.math.RoundingMode;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
@ -848,6 +850,42 @@ public class TbMathNodeTest {
 | 
			
		||||
        verify(ctx, never()).tellFailure(any(), any());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ParameterizedTest
 | 
			
		||||
    @MethodSource
 | 
			
		||||
    public void testCustomFunctions(String customFunction, double result) {
 | 
			
		||||
        var node = initNodeWithCustomFunction(customFunction,
 | 
			
		||||
                new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
 | 
			
		||||
                new TbMathArgument("a", TbMathArgumentType.MESSAGE_BODY, "argumentA"),
 | 
			
		||||
                new TbMathArgument("b", TbMathArgumentType.MESSAGE_BODY, "argumentB")
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        TbMsg msg = TbMsg.newMsg()
 | 
			
		||||
                .type(TbMsgType.POST_TELEMETRY_REQUEST)
 | 
			
		||||
                .originator(originator)
 | 
			
		||||
                .metaData(TbMsgMetaData.EMPTY)
 | 
			
		||||
                .data("{\"argumentA\":2,\"argumentB\":5}")
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        node.onMsg(ctx, msg);
 | 
			
		||||
 | 
			
		||||
        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
 | 
			
		||||
        verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
 | 
			
		||||
        TbMsg outMsg = msgCaptor.getValue();
 | 
			
		||||
        assertThat(outMsg).isNotNull();
 | 
			
		||||
        assertThat(outMsg.getData()).isNotNull();
 | 
			
		||||
        var resultJson = JacksonUtil.toJsonNode(outMsg.getData());
 | 
			
		||||
        assertThat(resultJson.has("result")).isTrue();
 | 
			
		||||
        assertThat(resultJson.get("result").asDouble()).isEqualTo(new BigDecimal(result).setScale(2, RoundingMode.HALF_UP).doubleValue());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Stream<Arguments> testCustomFunctions() {
 | 
			
		||||
        return Stream.of(
 | 
			
		||||
                Arguments.of("ln(a)", Math.log(2)),
 | 
			
		||||
                Arguments.of("lg(a)", Math.log10(2)),
 | 
			
		||||
                Arguments.of("logab(a, b)", Math.log(5) / Math.log(2))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static class RuleDispatcherExecutor extends AbstractListeningExecutor {
 | 
			
		||||
        @Override
 | 
			
		||||
        protected int getThreadPollSize() {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user