From 2e8fe17871f17d7d3f2a95864b4c65db8d99e65b Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 20 Nov 2025 16:11:31 +0200 Subject: [PATCH 1/2] Fix reevaluation interval for entity aggregation CFs --- .../service/cf/ctx/state/CalculatedFieldCtx.java | 11 +++++++++-- .../org/thingsboard/server/dao/util/TimeUtils.java | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index 4ed1b3ea01..393851eb02 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -58,6 +58,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.util.TimeUtils; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto; import org.thingsboard.server.service.cf.CalculatedFieldProcessingService; import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId; @@ -66,6 +67,7 @@ import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingCalculat import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import java.io.Closeable; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -215,8 +217,13 @@ public class CalculatedFieldCtx implements Closeable { if (watermark != null && watermark.getDuration() > 0) { return true; } - long intervalDurationMillis = entityAggregationConfig.getInterval().getCurrentIntervalDurationMillis(); - if (now - lastReevaluationTs >= intervalDurationMillis) { + if (lastReevaluationTs == 0) { + lastReevaluationTs = now; + return true; + } + ZonedDateTime lastReevaluationTime = TimeUtils.toZonedDateTime(lastReevaluationTs, entityAggregationConfig.getInterval().getZoneId()); + long previousIntervalEndTs = entityAggregationConfig.getInterval().getDateTimeIntervalEndTs(lastReevaluationTime); + if (now >= previousIntervalEndTs) { lastReevaluationTs = now; return true; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/TimeUtils.java b/dao/src/main/java/org/thingsboard/server/dao/util/TimeUtils.java index 8062ec6265..ad63192442 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/TimeUtils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/TimeUtils.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.util; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.kv.IntervalType; import java.time.Instant; @@ -24,6 +26,7 @@ import java.time.temporal.ChronoUnit; import java.time.temporal.IsoFields; import java.time.temporal.WeekFields; +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class TimeUtils { public static long calculateIntervalEnd(long startTs, IntervalType intervalType, ZoneId tzId) { @@ -42,4 +45,8 @@ public class TimeUtils { } } + public static ZonedDateTime toZonedDateTime(long ts, ZoneId zoneId) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(ts), zoneId); + } + } From b1c4343fdd89c03fb191a2f1c03490304714fef5 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 20 Nov 2025 18:44:03 +0200 Subject: [PATCH 2/2] Add help for alarm rule TBEL condition expression --- ...alarm-rule-condition-dialog.component.html | 4 +- .../help/en_US/alarm-rule/expression_fn.md | 47 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 ui-ngx/src/assets/help/en_US/alarm-rule/expression_fn.md diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html index b3c0028f87..feafcaa0a4 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.html @@ -63,7 +63,9 @@ [functionArgs]="functionArgs" [highlightRules]="argumentsHighlightRules" [editorCompleter]="argumentsEditorCompleter" - noValidate="true"> + noValidate="true" + [helpPopupStyle]="{ width: '1200px' }" + helpId="alarm-rule/expression_fn">
{{ 'alarm-rule.expression-type.script' | translate }}
diff --git a/ui-ngx/src/assets/help/en_US/alarm-rule/expression_fn.md b/ui-ngx/src/assets/help/en_US/alarm-rule/expression_fn.md new file mode 100644 index 0000000000..0670384bd8 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/alarm-rule/expression_fn.md @@ -0,0 +1,47 @@ +## Alarm rule condition TBEL script function + +The **expression()** function is a user-defined script that enables custom condition expressions using [TBEL](${siteBaseUrl}/docs${docPlatformPrefix}/user-guide/tbel/) on telemetry and attribute data. +It receives arguments configured in the alarm rule setup, along with an additional `ctx` object that stores `latestTs` and provides access to all arguments. + +### Function signature + +```javascript +function expression(ctx, arg1, arg2, ...): boolean +``` + +### Supported arguments + +There are two types of arguments supported in the alarm rule configuration: attributes and latest telemetry. + +These arguments are single values and may be of type: boolean, int64 (long), double, string, or JSON. + +### Usage + +**Example: Convert temperature from Fahrenheit to Celsius and raise the alarm if the Celsius value is greater than 36** + +```javascript +var temperatureC = (temperatureF - 32) / 1.8; +return temperatureC > 36; +``` + +Alternatively, use `ctx` to access the argument as an object: + +```json +{ + "temperatureF": { + "ts": 1740644636669, + "value": 98.7 + } +} +``` + +You may notice that the object includes both the `value` of an argument and its timestamp as `ts`. +The `ctx` object also includes the property `latestTs`, which represents the latest timestamp of the arguments telemetry in milliseconds. + +Let's modify the expression that converts Fahrenheit to Celsius to also check if the temperature's timestamp is exactly at the start of an hour: + +```javascript +var temperatureC = (ctx.args.temperatureF.value - 32) / 1.8; +var temperatureTs = ctx.args.temperatureF.ts; +return temperatureC > 36 && ((temperatureTs / 1000) % 3600) == 0; +```