Monitoring: automatic rule chain update
This commit is contained in:
		
							parent
							
								
									d6a4d454fd
								
							
						
					
					
						commit
						32212b9c51
					
				@ -25,6 +25,7 @@ import org.springframework.context.event.EventListener;
 | 
			
		||||
import org.springframework.scheduling.annotation.EnableScheduling;
 | 
			
		||||
import org.thingsboard.common.util.ThingsBoardExecutors;
 | 
			
		||||
import org.thingsboard.monitoring.service.BaseMonitoringService;
 | 
			
		||||
import org.thingsboard.monitoring.service.MonitoringEntityService;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
@ -38,6 +39,8 @@ public class ThingsboardMonitoringApplication {
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private List<BaseMonitoringService<?, ?>> monitoringServices;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private MonitoringEntityService entityService;
 | 
			
		||||
 | 
			
		||||
    @Value("${monitoring.monitoring_rate_ms}")
 | 
			
		||||
    private int monitoringRateMs;
 | 
			
		||||
@ -50,6 +53,9 @@ public class ThingsboardMonitoringApplication {
 | 
			
		||||
 | 
			
		||||
    @EventListener(ApplicationReadyEvent.class)
 | 
			
		||||
    public void startMonitoring() {
 | 
			
		||||
        entityService.checkEntities();
 | 
			
		||||
        monitoringServices.forEach(BaseMonitoringService::init);
 | 
			
		||||
 | 
			
		||||
        ScheduledExecutorService scheduler = ThingsBoardExecutors.newSingleThreadScheduledExecutor("monitoring-executor");
 | 
			
		||||
        scheduler.scheduleWithFixedDelay(() -> {
 | 
			
		||||
            monitoringServices.forEach(monitoringService -> {
 | 
			
		||||
 | 
			
		||||
@ -15,17 +15,15 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.monitoring.client;
 | 
			
		||||
 | 
			
		||||
import jakarta.annotation.PostConstruct;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
 | 
			
		||||
import org.springframework.boot.web.client.RestTemplateBuilder;
 | 
			
		||||
import org.springframework.context.annotation.Scope;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.thingsboard.rest.client.RestClient;
 | 
			
		||||
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
 | 
			
		||||
public class TbClient extends RestClient {
 | 
			
		||||
 | 
			
		||||
    @Value("${monitoring.rest.username}")
 | 
			
		||||
@ -41,6 +39,11 @@ public class TbClient extends RestClient {
 | 
			
		||||
                .build(), baseUrl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    private void init() {
 | 
			
		||||
        logIn();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String logIn() {
 | 
			
		||||
        login(username, password);
 | 
			
		||||
        return getToken();
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,6 @@ import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.thingsboard.monitoring.client.TbClient;
 | 
			
		||||
import org.thingsboard.monitoring.client.WsClient;
 | 
			
		||||
import org.thingsboard.monitoring.config.MonitoringConfig;
 | 
			
		||||
import org.thingsboard.monitoring.config.MonitoringTarget;
 | 
			
		||||
@ -46,6 +45,8 @@ public abstract class BaseHealthChecker<C extends MonitoringConfig, T extends Mo
 | 
			
		||||
 | 
			
		||||
    private Object info;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    protected MonitoringEntityService entityService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private MonitoringReporter reporter;
 | 
			
		||||
    @Autowired
 | 
			
		||||
@ -64,7 +65,7 @@ public abstract class BaseHealthChecker<C extends MonitoringConfig, T extends Mo
 | 
			
		||||
        info = getInfo();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected abstract void initialize(TbClient tbClient);
 | 
			
		||||
    protected abstract void initialize();
 | 
			
		||||
 | 
			
		||||
    public final void check(WsClient wsClient) {
 | 
			
		||||
        log.debug("[{}] Checking", info);
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,6 @@
 | 
			
		||||
package org.thingsboard.monitoring.service;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Sets;
 | 
			
		||||
import jakarta.annotation.PostConstruct;
 | 
			
		||||
import lombok.SneakyThrows;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.lang3.StringUtils;
 | 
			
		||||
@ -85,12 +84,11 @@ public abstract class BaseMonitoringService<C extends MonitoringConfig<T>, T ext
 | 
			
		||||
    @Value("${monitoring.calculated_fields.enabled:true}")
 | 
			
		||||
    protected boolean checkCalculatedFields;
 | 
			
		||||
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    private void init() {
 | 
			
		||||
    public void init() {
 | 
			
		||||
        if (configs == null || configs.isEmpty()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        tbClient.logIn();
 | 
			
		||||
 | 
			
		||||
        configs.forEach(config -> {
 | 
			
		||||
            config.getTargets().forEach(target -> {
 | 
			
		||||
                BaseHealthChecker<C, T> healthChecker = initHealthChecker(target, config);
 | 
			
		||||
@ -108,7 +106,7 @@ public abstract class BaseMonitoringService<C extends MonitoringConfig<T>, T ext
 | 
			
		||||
    private BaseHealthChecker<C, T> initHealthChecker(T target, C config) {
 | 
			
		||||
        BaseHealthChecker<C, T> healthChecker = (BaseHealthChecker<C, T>) createHealthChecker(config, target);
 | 
			
		||||
        log.info("Initializing {} for {}", healthChecker.getClass().getSimpleName(), target.getBaseUrl());
 | 
			
		||||
        healthChecker.initialize(tbClient);
 | 
			
		||||
        healthChecker.initialize();
 | 
			
		||||
        devices.add(target.getDeviceId());
 | 
			
		||||
        return healthChecker;
 | 
			
		||||
    }
 | 
			
		||||
@ -140,7 +138,7 @@ public abstract class BaseMonitoringService<C extends MonitoringConfig<T>, T ext
 | 
			
		||||
                reporter.serviceIsOk(MonitoredServiceKey.EDQS);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            reporter.reportLatencies(tbClient);
 | 
			
		||||
            reporter.reportLatencies();
 | 
			
		||||
            log.debug("Finished {}", getName());
 | 
			
		||||
        } catch (ServiceFailureException e) {
 | 
			
		||||
            reporter.serviceFailure(e.getServiceKey(), e);
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,245 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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.monitoring.service;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.lang3.RandomStringUtils;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
import org.thingsboard.common.util.RegexUtils;
 | 
			
		||||
import org.thingsboard.monitoring.client.TbClient;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.DeviceConfig;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.TransportMonitoringConfig;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.TransportType;
 | 
			
		||||
import org.thingsboard.monitoring.util.ResourceUtils;
 | 
			
		||||
import org.thingsboard.server.common.data.Device;
 | 
			
		||||
import org.thingsboard.server.common.data.DeviceProfile;
 | 
			
		||||
import org.thingsboard.server.common.data.DeviceProfileType;
 | 
			
		||||
import org.thingsboard.server.common.data.DeviceTransportType;
 | 
			
		||||
import org.thingsboard.server.common.data.TbResource;
 | 
			
		||||
import org.thingsboard.server.common.data.asset.Asset;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.Argument;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.Output;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.OutputType;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
 | 
			
		||||
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.data.DeviceData;
 | 
			
		||||
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
 | 
			
		||||
import org.thingsboard.server.common.data.id.RuleChainId;
 | 
			
		||||
import org.thingsboard.server.common.data.kv.KvEntry;
 | 
			
		||||
import org.thingsboard.server.common.data.page.PageLink;
 | 
			
		||||
import org.thingsboard.server.common.data.rule.RuleChain;
 | 
			
		||||
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
 | 
			
		||||
import org.thingsboard.server.common.data.rule.RuleChainType;
 | 
			
		||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
 | 
			
		||||
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import static org.thingsboard.monitoring.service.BaseHealthChecker.TEST_CF_TELEMETRY_KEY;
 | 
			
		||||
import static org.thingsboard.monitoring.service.BaseHealthChecker.TEST_TELEMETRY_KEY;
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
public class MonitoringEntityService {
 | 
			
		||||
 | 
			
		||||
    private final TbClient tbClient;
 | 
			
		||||
 | 
			
		||||
    @Value("${monitoring.calculated_fields.enabled:true}")
 | 
			
		||||
    private boolean calculatedFieldsMonitoringEnabled;
 | 
			
		||||
 | 
			
		||||
    public void checkEntities() {
 | 
			
		||||
        RuleChain ruleChain = tbClient.getRuleChains(RuleChainType.CORE, new PageLink(10)).getData().stream()
 | 
			
		||||
                .filter(RuleChain::isRoot)
 | 
			
		||||
                .findFirst().orElseThrow();
 | 
			
		||||
        RuleChainId ruleChainId = ruleChain.getId();
 | 
			
		||||
 | 
			
		||||
        JsonNode ruleChainDescriptor = ResourceUtils.getResource("rule_chain.json");
 | 
			
		||||
        List<String> attributeKeys = tbClient.getAttributeKeys(ruleChainId);
 | 
			
		||||
        Map<String, String> attributes = tbClient.getAttributeKvEntries(ruleChainId, attributeKeys).stream()
 | 
			
		||||
                .collect(Collectors.toMap(KvEntry::getKey, KvEntry::getValueAsString));
 | 
			
		||||
 | 
			
		||||
        int currentVersion = Integer.parseInt(attributes.getOrDefault("version", "0"));
 | 
			
		||||
        int newVersion = ruleChainDescriptor.get("version").asInt();
 | 
			
		||||
        if (currentVersion == newVersion) {
 | 
			
		||||
            log.info("Not updating rule chain, version is the same ({})", currentVersion);
 | 
			
		||||
            return;
 | 
			
		||||
        } else {
 | 
			
		||||
            log.info("Updating rule chain '{}' from version {} to {}", ruleChain.getName(), currentVersion, newVersion);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String metadataJson = RegexUtils.replace(ruleChainDescriptor.get("metadata").toString(),
 | 
			
		||||
                "\\$\\{MONITORING:(.+?)}", matchResult -> {
 | 
			
		||||
                    String key = matchResult.group(1);
 | 
			
		||||
                    String value = attributes.get(key);
 | 
			
		||||
                    if (value == null) {
 | 
			
		||||
                        throw new IllegalArgumentException("No attribute found for key " + key);
 | 
			
		||||
                    }
 | 
			
		||||
                    log.info("Using {}: {}", key, value);
 | 
			
		||||
                    return value;
 | 
			
		||||
                });
 | 
			
		||||
        RuleChainMetaData metaData = JacksonUtil.fromString(metadataJson, RuleChainMetaData.class);
 | 
			
		||||
        metaData.setRuleChainId(ruleChainId);
 | 
			
		||||
        tbClient.saveRuleChainMetaData(metaData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Asset getOrCreateMonitoringAsset() {
 | 
			
		||||
        String assetName = "[Monitoring] Latencies";
 | 
			
		||||
        return tbClient.findAsset(assetName).orElseGet(() -> {
 | 
			
		||||
            Asset asset = new Asset();
 | 
			
		||||
            asset.setType("Monitoring");
 | 
			
		||||
            asset.setName(assetName);
 | 
			
		||||
            asset = tbClient.saveAsset(asset);
 | 
			
		||||
            log.info("Created monitoring asset {}", asset.getId());
 | 
			
		||||
            return asset;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void checkEntities(TransportMonitoringConfig config, TransportMonitoringTarget target) {
 | 
			
		||||
        Device device = getOrCreateDevice(config, target);
 | 
			
		||||
        DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(device.getId())
 | 
			
		||||
                .orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + device.getId()));
 | 
			
		||||
 | 
			
		||||
        DeviceConfig deviceConfig = new DeviceConfig();
 | 
			
		||||
        deviceConfig.setId(device.getId().toString());
 | 
			
		||||
        deviceConfig.setName(device.getName());
 | 
			
		||||
        deviceConfig.setCredentials(credentials);
 | 
			
		||||
        target.setDevice(deviceConfig);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Device getOrCreateDevice(TransportMonitoringConfig config, TransportMonitoringTarget target) {
 | 
			
		||||
        TransportType transportType = config.getTransportType();
 | 
			
		||||
        String deviceName = String.format("%s %s (%s) - %s", target.getNamePrefix(), transportType.getName(), target.getQueue(), target.getBaseUrl()).trim();
 | 
			
		||||
        Device device = tbClient.getTenantDevice(deviceName).orElse(null);
 | 
			
		||||
        if (device != null) {
 | 
			
		||||
            if (calculatedFieldsMonitoringEnabled) {
 | 
			
		||||
                CalculatedField calculatedField = tbClient.getCalculatedFieldsByEntityId(device.getId(), new PageLink(1, 0, TEST_CF_TELEMETRY_KEY))
 | 
			
		||||
                        .getData().stream().findFirst().orElse(null);
 | 
			
		||||
                if (calculatedField == null) {
 | 
			
		||||
                    createCalculatedField(device);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return device;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        log.info("Creating new device '{}'", deviceName);
 | 
			
		||||
        device = new Device();
 | 
			
		||||
        device.setName(deviceName);
 | 
			
		||||
 | 
			
		||||
        DeviceCredentials credentials = new DeviceCredentials();
 | 
			
		||||
        credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20));
 | 
			
		||||
        DeviceData deviceData = new DeviceData();
 | 
			
		||||
        deviceData.setConfiguration(new DefaultDeviceConfiguration());
 | 
			
		||||
 | 
			
		||||
        DeviceProfile deviceProfile = getOrCreateDeviceProfile(config, target);
 | 
			
		||||
        device.setType(deviceProfile.getName());
 | 
			
		||||
        device.setDeviceProfileId(deviceProfile.getId());
 | 
			
		||||
 | 
			
		||||
        if (transportType != TransportType.LWM2M) {
 | 
			
		||||
            deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration());
 | 
			
		||||
            credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
 | 
			
		||||
        } else {
 | 
			
		||||
            deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration());
 | 
			
		||||
            credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
 | 
			
		||||
            LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials();
 | 
			
		||||
            NoSecClientCredential client = new NoSecClientCredential();
 | 
			
		||||
            client.setEndpoint(credentials.getCredentialsId());
 | 
			
		||||
            lwm2mCreds.setClient(client);
 | 
			
		||||
            LwM2MBootstrapClientCredentials bootstrap = new LwM2MBootstrapClientCredentials();
 | 
			
		||||
            bootstrap.setBootstrapServer(new NoSecBootstrapClientCredential());
 | 
			
		||||
            bootstrap.setLwm2mServer(new NoSecBootstrapClientCredential());
 | 
			
		||||
            lwm2mCreds.setBootstrap(bootstrap);
 | 
			
		||||
            credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return tbClient.saveDeviceWithCredentials(device, credentials).get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private DeviceProfile getOrCreateDeviceProfile(TransportMonitoringConfig config, TransportMonitoringTarget target) {
 | 
			
		||||
        TransportType transportType = config.getTransportType();
 | 
			
		||||
        String profileName = String.format("%s %s (%s)", target.getNamePrefix(), transportType.getName(), target.getQueue()).trim();
 | 
			
		||||
        DeviceProfile deviceProfile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData()
 | 
			
		||||
                .stream().findFirst().orElse(null);
 | 
			
		||||
        if (deviceProfile != null) {
 | 
			
		||||
            return deviceProfile;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        log.info("Creating new device profile '{}'", profileName);
 | 
			
		||||
        if (transportType != TransportType.LWM2M) {
 | 
			
		||||
            deviceProfile = new DeviceProfile();
 | 
			
		||||
            deviceProfile.setType(DeviceProfileType.DEFAULT);
 | 
			
		||||
            deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
 | 
			
		||||
            DeviceProfileData profileData = new DeviceProfileData();
 | 
			
		||||
            profileData.setConfiguration(new DefaultDeviceProfileConfiguration());
 | 
			
		||||
            profileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
 | 
			
		||||
            deviceProfile.setProfileData(profileData);
 | 
			
		||||
        } else {
 | 
			
		||||
            tbClient.getResources(new PageLink(1, 0, "LwM2M Monitoring")).getData()
 | 
			
		||||
                    .stream().findFirst()
 | 
			
		||||
                    .orElseGet(() -> {
 | 
			
		||||
                        TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class);
 | 
			
		||||
                        log.info("Creating LwM2M resource");
 | 
			
		||||
                        return tbClient.saveResource(newResource);
 | 
			
		||||
                    });
 | 
			
		||||
            deviceProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        deviceProfile.setName(profileName);
 | 
			
		||||
        deviceProfile.setDefaultQueueName(target.getQueue());
 | 
			
		||||
        return tbClient.saveDeviceProfile(deviceProfile);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void createCalculatedField(Device device) {
 | 
			
		||||
        log.info("Creating calculated field for device '{}'", device.getName());
 | 
			
		||||
        CalculatedField calculatedField = new CalculatedField();
 | 
			
		||||
        calculatedField.setName(TEST_CF_TELEMETRY_KEY);
 | 
			
		||||
        calculatedField.setEntityId(device.getId());
 | 
			
		||||
        calculatedField.setType(CalculatedFieldType.SCRIPT);
 | 
			
		||||
        ScriptCalculatedFieldConfiguration configuration = new ScriptCalculatedFieldConfiguration();
 | 
			
		||||
        Argument testDataArgument = new Argument();
 | 
			
		||||
        testDataArgument.setRefEntityKey(new ReferencedEntityKey(TEST_TELEMETRY_KEY, ArgumentType.TS_LATEST, null));
 | 
			
		||||
        configuration.setArguments(Map.of(
 | 
			
		||||
                TEST_TELEMETRY_KEY, testDataArgument
 | 
			
		||||
        ));
 | 
			
		||||
        configuration.setExpression("return { \"" + TEST_CF_TELEMETRY_KEY + "\": " + TEST_TELEMETRY_KEY + " + \"-cf\" };");
 | 
			
		||||
        Output output = new Output();
 | 
			
		||||
        output.setType(OutputType.TIME_SERIES);
 | 
			
		||||
        configuration.setOutput(output);
 | 
			
		||||
        calculatedField.setConfiguration(configuration);
 | 
			
		||||
        calculatedField.setDebugMode(true);
 | 
			
		||||
        tbClient.saveCalculatedField(calculatedField);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -46,6 +46,8 @@ import java.util.stream.Collectors;
 | 
			
		||||
public class MonitoringReporter {
 | 
			
		||||
 | 
			
		||||
    private final NotificationService notificationService;
 | 
			
		||||
    private final TbClient tbClient;
 | 
			
		||||
    private final MonitoringEntityService entityService;
 | 
			
		||||
 | 
			
		||||
    private final Map<String, Latency> latencies = new ConcurrentHashMap<>();
 | 
			
		||||
    private final Map<Object, AtomicInteger> failuresCounters = new ConcurrentHashMap<>();
 | 
			
		||||
@ -62,7 +64,7 @@ public class MonitoringReporter {
 | 
			
		||||
    @Value("${monitoring.latency.reporting_asset_id}")
 | 
			
		||||
    private String reportingAssetId;
 | 
			
		||||
 | 
			
		||||
    public void reportLatencies(TbClient tbClient) {
 | 
			
		||||
    public void reportLatencies() {
 | 
			
		||||
        if (latencies.isEmpty()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@ -81,15 +83,7 @@ public class MonitoringReporter {
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            if (StringUtils.isBlank(reportingAssetId)) {
 | 
			
		||||
                String assetName = "[Monitoring] Latencies";
 | 
			
		||||
                Asset monitoringAsset = tbClient.findAsset(assetName).orElseGet(() -> {
 | 
			
		||||
                    Asset asset = new Asset();
 | 
			
		||||
                    asset.setType("Monitoring");
 | 
			
		||||
                    asset.setName(assetName);
 | 
			
		||||
                    asset = tbClient.saveAsset(asset);
 | 
			
		||||
                    log.info("Created monitoring asset {}", asset.getId());
 | 
			
		||||
                    return asset;
 | 
			
		||||
                });
 | 
			
		||||
                Asset monitoringAsset = entityService.getOrCreateMonitoringAsset();
 | 
			
		||||
                reportingAssetId = monitoringAsset.getId().toString();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -17,46 +17,13 @@ package org.thingsboard.monitoring.service.transport;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.node.TextNode;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.lang3.RandomStringUtils;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
import org.thingsboard.monitoring.client.TbClient;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.DeviceConfig;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.TransportInfo;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.TransportMonitoringConfig;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.TransportMonitoringTarget;
 | 
			
		||||
import org.thingsboard.monitoring.config.transport.TransportType;
 | 
			
		||||
import org.thingsboard.monitoring.service.BaseHealthChecker;
 | 
			
		||||
import org.thingsboard.monitoring.util.ResourceUtils;
 | 
			
		||||
import org.thingsboard.server.common.data.Device;
 | 
			
		||||
import org.thingsboard.server.common.data.DeviceProfile;
 | 
			
		||||
import org.thingsboard.server.common.data.DeviceProfileType;
 | 
			
		||||
import org.thingsboard.server.common.data.DeviceTransportType;
 | 
			
		||||
import org.thingsboard.server.common.data.TbResource;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.Argument;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.Output;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.OutputType;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential;
 | 
			
		||||
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
 | 
			
		||||
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.data.DeviceData;
 | 
			
		||||
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
 | 
			
		||||
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
 | 
			
		||||
import org.thingsboard.server.common.data.page.PageLink;
 | 
			
		||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
 | 
			
		||||
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
public abstract class TransportHealthChecker<C extends TransportMonitoringConfig> extends BaseHealthChecker<C, TransportMonitoringTarget> {
 | 
			
		||||
@ -69,16 +36,8 @@ public abstract class TransportHealthChecker<C extends TransportMonitoringConfig
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void initialize(TbClient tbClient) {
 | 
			
		||||
        Device device = getOrCreateDevice(tbClient);
 | 
			
		||||
        DeviceCredentials credentials = tbClient.getDeviceCredentialsByDeviceId(device.getId())
 | 
			
		||||
                .orElseThrow(() -> new IllegalArgumentException("No credentials found for device " + device.getId()));
 | 
			
		||||
 | 
			
		||||
        DeviceConfig deviceConfig = new DeviceConfig();
 | 
			
		||||
        deviceConfig.setId(device.getId().toString());
 | 
			
		||||
        deviceConfig.setName(device.getName());
 | 
			
		||||
        deviceConfig.setCredentials(credentials);
 | 
			
		||||
        target.setDevice(deviceConfig);
 | 
			
		||||
    protected void initialize() {
 | 
			
		||||
        entityService.checkEntities(config, target);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@ -98,110 +57,6 @@ public abstract class TransportHealthChecker<C extends TransportMonitoringConfig
 | 
			
		||||
 | 
			
		||||
    protected abstract TransportType getTransportType();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private Device getOrCreateDevice(TbClient tbClient) {
 | 
			
		||||
        TransportType transportType = config.getTransportType();
 | 
			
		||||
        String deviceName = String.format("%s %s (%s) - %s", target.getNamePrefix(), transportType.getName(), target.getQueue(), target.getBaseUrl()).trim();
 | 
			
		||||
        Device device = tbClient.getTenantDevice(deviceName).orElse(null);
 | 
			
		||||
        if (device != null) {
 | 
			
		||||
            if (isCfMonitoringEnabled()) {
 | 
			
		||||
                CalculatedField calculatedField = tbClient.getCalculatedFieldsByEntityId(device.getId(), new PageLink(1, 0, TEST_CF_TELEMETRY_KEY))
 | 
			
		||||
                        .getData().stream().findFirst().orElse(null);
 | 
			
		||||
                if (calculatedField == null) {
 | 
			
		||||
                    createCalculatedField(tbClient, device);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return device;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        log.info("Creating new device '{}'", deviceName);
 | 
			
		||||
        device = new Device();
 | 
			
		||||
        device.setName(deviceName);
 | 
			
		||||
 | 
			
		||||
        DeviceCredentials credentials = new DeviceCredentials();
 | 
			
		||||
        credentials.setCredentialsId(RandomStringUtils.randomAlphabetic(20));
 | 
			
		||||
        DeviceData deviceData = new DeviceData();
 | 
			
		||||
        deviceData.setConfiguration(new DefaultDeviceConfiguration());
 | 
			
		||||
 | 
			
		||||
        DeviceProfile deviceProfile = getOrCreateDeviceProfile(tbClient);
 | 
			
		||||
        device.setType(deviceProfile.getName());
 | 
			
		||||
        device.setDeviceProfileId(deviceProfile.getId());
 | 
			
		||||
 | 
			
		||||
        if (transportType != TransportType.LWM2M) {
 | 
			
		||||
            deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration());
 | 
			
		||||
            credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
 | 
			
		||||
        } else {
 | 
			
		||||
            deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration());
 | 
			
		||||
            credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
 | 
			
		||||
            LwM2MDeviceCredentials lwm2mCreds = new LwM2MDeviceCredentials();
 | 
			
		||||
            NoSecClientCredential client = new NoSecClientCredential();
 | 
			
		||||
            client.setEndpoint(credentials.getCredentialsId());
 | 
			
		||||
            lwm2mCreds.setClient(client);
 | 
			
		||||
            LwM2MBootstrapClientCredentials bootstrap = new LwM2MBootstrapClientCredentials();
 | 
			
		||||
            bootstrap.setBootstrapServer(new NoSecBootstrapClientCredential());
 | 
			
		||||
            bootstrap.setLwm2mServer(new NoSecBootstrapClientCredential());
 | 
			
		||||
            lwm2mCreds.setBootstrap(bootstrap);
 | 
			
		||||
            credentials.setCredentialsValue(JacksonUtil.toString(lwm2mCreds));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return tbClient.saveDeviceWithCredentials(device, credentials).get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private DeviceProfile getOrCreateDeviceProfile(TbClient tbClient) {
 | 
			
		||||
        TransportType transportType = config.getTransportType();
 | 
			
		||||
        String profileName = String.format("%s %s (%s)", target.getNamePrefix(), transportType.getName(), target.getQueue()).trim();
 | 
			
		||||
        DeviceProfile deviceProfile = tbClient.getDeviceProfiles(new PageLink(1, 0, profileName)).getData()
 | 
			
		||||
                .stream().findFirst().orElse(null);
 | 
			
		||||
        if (deviceProfile != null) {
 | 
			
		||||
            return deviceProfile;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        log.info("Creating new device profile '{}'", profileName);
 | 
			
		||||
        if (transportType != TransportType.LWM2M) {
 | 
			
		||||
            deviceProfile = new DeviceProfile();
 | 
			
		||||
            deviceProfile.setType(DeviceProfileType.DEFAULT);
 | 
			
		||||
            deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
 | 
			
		||||
            DeviceProfileData profileData = new DeviceProfileData();
 | 
			
		||||
            profileData.setConfiguration(new DefaultDeviceProfileConfiguration());
 | 
			
		||||
            profileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
 | 
			
		||||
            deviceProfile.setProfileData(profileData);
 | 
			
		||||
        } else {
 | 
			
		||||
            tbClient.getResources(new PageLink(1, 0, "LwM2M Monitoring")).getData()
 | 
			
		||||
                    .stream().findFirst()
 | 
			
		||||
                    .orElseGet(() -> {
 | 
			
		||||
                        TbResource newResource = ResourceUtils.getResource("lwm2m/resource.json", TbResource.class);
 | 
			
		||||
                        log.info("Creating LwM2M resource");
 | 
			
		||||
                        return tbClient.saveResource(newResource);
 | 
			
		||||
                    });
 | 
			
		||||
            deviceProfile = ResourceUtils.getResource("lwm2m/device_profile.json", DeviceProfile.class);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        deviceProfile.setName(profileName);
 | 
			
		||||
        deviceProfile.setDefaultQueueName(target.getQueue());
 | 
			
		||||
        return tbClient.saveDeviceProfile(deviceProfile);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void createCalculatedField(TbClient tbClient, Device device) {
 | 
			
		||||
        log.info("Creating calculated field for device '{}'", device.getName());
 | 
			
		||||
        CalculatedField calculatedField = new CalculatedField();
 | 
			
		||||
        calculatedField.setName(TEST_CF_TELEMETRY_KEY);
 | 
			
		||||
        calculatedField.setEntityId(device.getId());
 | 
			
		||||
        calculatedField.setType(CalculatedFieldType.SCRIPT);
 | 
			
		||||
        ScriptCalculatedFieldConfiguration configuration = new ScriptCalculatedFieldConfiguration();
 | 
			
		||||
        Argument testDataArgument = new Argument();
 | 
			
		||||
        testDataArgument.setRefEntityKey(new ReferencedEntityKey(TEST_TELEMETRY_KEY, ArgumentType.TS_LATEST, null));
 | 
			
		||||
        configuration.setArguments(Map.of(
 | 
			
		||||
                TEST_TELEMETRY_KEY, testDataArgument
 | 
			
		||||
        ));
 | 
			
		||||
        configuration.setExpression("return { \"" + TEST_CF_TELEMETRY_KEY + "\": " + TEST_TELEMETRY_KEY + " + \"-cf\" };");
 | 
			
		||||
        Output output = new Output();
 | 
			
		||||
        output.setType(OutputType.TIME_SERIES);
 | 
			
		||||
        configuration.setOutput(output);
 | 
			
		||||
        calculatedField.setConfiguration(configuration);
 | 
			
		||||
        calculatedField.setDebugMode(true);
 | 
			
		||||
        tbClient.saveCalculatedField(calculatedField);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected boolean isCfMonitoringEnabled() {
 | 
			
		||||
        return calculatedFieldsMonitoringEnabled;
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.monitoring.util;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import lombok.SneakyThrows;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
 | 
			
		||||
@ -24,14 +25,21 @@ public class ResourceUtils {
 | 
			
		||||
 | 
			
		||||
    @SneakyThrows
 | 
			
		||||
    public static <T> T getResource(String path, Class<T> type) {
 | 
			
		||||
        InputStream resource = ResourceUtils.class.getClassLoader().getResourceAsStream(path);
 | 
			
		||||
        if (resource == null) {
 | 
			
		||||
            throw new IllegalArgumentException("Resource not found for path " + path);
 | 
			
		||||
        }
 | 
			
		||||
        InputStream resource = getResourceStream(path);
 | 
			
		||||
        return JacksonUtil.OBJECT_MAPPER.readValue(resource, type);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SneakyThrows
 | 
			
		||||
    public static JsonNode getResource(String path) {
 | 
			
		||||
        InputStream resource = getResourceStream(path);
 | 
			
		||||
        return JacksonUtil.OBJECT_MAPPER.readTree(resource);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static InputStream getResourceAsStream(String path) {
 | 
			
		||||
        return getResourceStream(path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static InputStream getResourceStream(String path) {
 | 
			
		||||
        InputStream resource = ResourceUtils.class.getClassLoader().getResourceAsStream(path);
 | 
			
		||||
        if (resource == null) {
 | 
			
		||||
            throw new IllegalArgumentException("Resource not found for path " + path);
 | 
			
		||||
 | 
			
		||||
@ -1,257 +1,232 @@
 | 
			
		||||
{
 | 
			
		||||
  "version": 1,
 | 
			
		||||
  "ruleChain": {
 | 
			
		||||
    "additionalInfo": null,
 | 
			
		||||
    "name": "Root Rule Chain",
 | 
			
		||||
    "type": "CORE",
 | 
			
		||||
    "firstRuleNodeId": null,
 | 
			
		||||
    "root": false,
 | 
			
		||||
    "debugMode": false,
 | 
			
		||||
    "configuration": null,
 | 
			
		||||
    "externalId": null
 | 
			
		||||
    "additionalInfo": null
 | 
			
		||||
  },
 | 
			
		||||
  "metadata": {
 | 
			
		||||
    "firstNodeIndex": 12,
 | 
			
		||||
    "firstNodeIndex": 9,
 | 
			
		||||
    "nodes": [
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": null,
 | 
			
		||||
          "layoutX": 1202,
 | 
			
		||||
          "layoutY": 221
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
 | 
			
		||||
        "name": "Save Timeseries",
 | 
			
		||||
        "debugSettings": {
 | 
			
		||||
          "failuresEnabled": true,
 | 
			
		||||
          "allEnabled": false,
 | 
			
		||||
          "allEnabledUntil": 1735310701003
 | 
			
		||||
        },
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 1,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "defaultTTL": 0,
 | 
			
		||||
          "useServerTs": false,
 | 
			
		||||
          "processingSettings": {
 | 
			
		||||
            "type": "ON_EVERY_MESSAGE"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": null,
 | 
			
		||||
          "layoutX": 1202,
 | 
			
		||||
          "layoutY": 221
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 1000,
 | 
			
		||||
          "layoutY": 167
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
 | 
			
		||||
        "name": "Save Attributes",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 3,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "scope": "CLIENT_SCOPE",
 | 
			
		||||
          "notifyDevice": false,
 | 
			
		||||
          "processingSettings": {
 | 
			
		||||
            "type": "ON_EVERY_MESSAGE"
 | 
			
		||||
          },
 | 
			
		||||
          "scope": "CLIENT_SCOPE",
 | 
			
		||||
          "notifyDevice": false,
 | 
			
		||||
          "sendAttributesUpdatedNotification": false,
 | 
			
		||||
          "updateAttributesOnlyOnValueChange": false
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 1000,
 | 
			
		||||
          "layoutY": 167
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 566,
 | 
			
		||||
          "layoutY": 302
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
 | 
			
		||||
        "name": "Message Type Switch",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "version": 0
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 566,
 | 
			
		||||
          "layoutY": 302
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.action.TbLogNode",
 | 
			
		||||
        "name": "Log RPC from Device",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "scriptLang": "TBEL",
 | 
			
		||||
          "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
 | 
			
		||||
          "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
 | 
			
		||||
        },
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 1000,
 | 
			
		||||
          "layoutY": 381
 | 
			
		||||
        },
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.action.TbLogNode",
 | 
			
		||||
        "name": "Log RPC from Device",
 | 
			
		||||
        "name": "Log Other",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "scriptLang": "TBEL",
 | 
			
		||||
          "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
 | 
			
		||||
          "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 1000,
 | 
			
		||||
          "layoutY": 494
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.action.TbLogNode",
 | 
			
		||||
        "name": "Log Other",
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "scriptLang": "TBEL",
 | 
			
		||||
          "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
 | 
			
		||||
          "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 1000,
 | 
			
		||||
          "layoutY": 583
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
 | 
			
		||||
        "name": "RPC Call Request",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "timeoutInSeconds": 60
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 1000,
 | 
			
		||||
          "layoutY": 583
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 255,
 | 
			
		||||
          "layoutY": 301
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.filter.TbOriginatorTypeFilterNode",
 | 
			
		||||
        "name": "Is Entity Group",
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "originatorTypes": [
 | 
			
		||||
            "ENTITY_GROUP"
 | 
			
		||||
          ]
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 319,
 | 
			
		||||
          "layoutY": 109
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode",
 | 
			
		||||
        "name": "Post attributes or RPC request",
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "messageTypes": [
 | 
			
		||||
            "POST_ATTRIBUTES_REQUEST",
 | 
			
		||||
            "RPC_CALL_FROM_SERVER_TO_DEVICE"
 | 
			
		||||
          ]
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "layoutX": 627,
 | 
			
		||||
          "layoutY": 108
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.transform.TbDuplicateMsgToGroupNode",
 | 
			
		||||
        "name": "Duplicate To Group Entities",
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "entityGroupId": null,
 | 
			
		||||
          "entityGroupIsMessageOriginator": true
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
 | 
			
		||||
          "layoutX": 45,
 | 
			
		||||
          "layoutY": 359
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
 | 
			
		||||
        "name": "Device Profile Node",
 | 
			
		||||
        "debugSettings": {
 | 
			
		||||
          "failuresEnabled": true,
 | 
			
		||||
          "allEnabled": false,
 | 
			
		||||
          "allEnabledUntil": 1735310701003
 | 
			
		||||
        },
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 1,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "persistAlarmRulesState": false,
 | 
			
		||||
          "fetchAlarmRulesStateOnStart": false
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
 | 
			
		||||
          "layoutX": 45,
 | 
			
		||||
          "layoutY": 359
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "",
 | 
			
		||||
          "layoutX": 160,
 | 
			
		||||
          "layoutY": 631
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
 | 
			
		||||
        "name": "Test JS script",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "scriptLang": "JS",
 | 
			
		||||
          "jsScript": "var test = {\n    a: 'a',\n    b: 'b'\n};\nreturn test.a === 'a' && test.b === 'b';",
 | 
			
		||||
          "jsScript": "var test = {\n    a: 'a',\n    b: 'b'\n};\n\nreturn test.a === 'a' && test.b === 'b';",
 | 
			
		||||
          "tbelScript": "return msg.temperature > 20;"
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "dashboardId: ${MONITORING:dashboardId}",
 | 
			
		||||
          "layoutX": 251,
 | 
			
		||||
          "layoutY": 499
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "",
 | 
			
		||||
          "layoutX": 427,
 | 
			
		||||
          "layoutY": 541
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
 | 
			
		||||
        "name": "Test TBEL script",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "scriptLang": "TBEL",
 | 
			
		||||
          "jsScript": "return msg.temperature > 20;",
 | 
			
		||||
          "tbelScript": "var a = \"a\";\nvar b = \"b\";\nreturn a.equals(\"a\") && b.equals(\"b\");"
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "",
 | 
			
		||||
          "layoutX": 40,
 | 
			
		||||
          "layoutY": 252
 | 
			
		||||
        },
 | 
			
		||||
          "layoutX": 317,
 | 
			
		||||
          "layoutY": 355
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
 | 
			
		||||
        "name": "Add arrival timestamp",
 | 
			
		||||
        "debugSettings": {
 | 
			
		||||
          "failuresEnabled": true,
 | 
			
		||||
          "allEnabled": false,
 | 
			
		||||
          "allEnabledUntil": 1744642101587
 | 
			
		||||
        },
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "scriptLang": "TBEL",
 | 
			
		||||
          "jsScript": "return {msg: msg, metadata: metadata, msgType: msgType};",
 | 
			
		||||
          "tbelScript": "metadata.arrivalTs = Date.now();\nreturn {msg: msg, metadata: metadata, msgType: msgType};"
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "",
 | 
			
		||||
          "layoutX": 1467,
 | 
			
		||||
          "layoutY": 267
 | 
			
		||||
        },
 | 
			
		||||
          "layoutX": 40,
 | 
			
		||||
          "layoutY": 252
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode",
 | 
			
		||||
        "name": "Calculate additional latencies",
 | 
			
		||||
        "debugSettings": {
 | 
			
		||||
          "failuresEnabled": true,
 | 
			
		||||
          "allEnabled": false,
 | 
			
		||||
          "allEnabledUntil": 1735310701003
 | 
			
		||||
        },
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "scriptLang": "TBEL",
 | 
			
		||||
          "jsScript": "return {msg: msg, metadata: metadata, msgType: msgType};",
 | 
			
		||||
          "tbelScript": "var arrivalLatency = metadata.arrivalTs - metadata.ts;\nvar processingTime = Date.now() - metadata.arrivalTs;\nmsg = {\n    arrivalLatency: arrivalLatency,\n    processingTime: processingTime\n};\nreturn {msg: msg, metadata: metadata, msgType: msgType};"
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "",
 | 
			
		||||
          "layoutX": 1438,
 | 
			
		||||
          "layoutY": 403
 | 
			
		||||
        },
 | 
			
		||||
          "layoutX": 1467,
 | 
			
		||||
          "layoutY": 267
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.transform.TbChangeOriginatorNode",
 | 
			
		||||
        "name": "To latencies asset",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "originatorSource": "ENTITY",
 | 
			
		||||
@ -269,64 +244,79 @@
 | 
			
		||||
            "fetchLastLevelOnly": false
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "",
 | 
			
		||||
          "layoutX": 1438,
 | 
			
		||||
          "layoutY": 403
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
 | 
			
		||||
        "name": "Save Timeseries",
 | 
			
		||||
        "debugSettings": {
 | 
			
		||||
          "failuresEnabled": true,
 | 
			
		||||
          "allEnabled": false,
 | 
			
		||||
          "allEnabledUntil": 1735310701003
 | 
			
		||||
        },
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 1,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "defaultTTL": 0,
 | 
			
		||||
          "processingSettings": {
 | 
			
		||||
            "type": "ON_EVERY_MESSAGE"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": null,
 | 
			
		||||
          "layoutX": 1458,
 | 
			
		||||
          "layoutY": 505
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
 | 
			
		||||
        "name": "Save Timeseries",
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "configurationVersion": 1,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "defaultTTL": 0,
 | 
			
		||||
          "useServerTs": false,
 | 
			
		||||
          "processingSettings": {
 | 
			
		||||
            "type": "ON_EVERY_MESSAGE"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.filter.TbCheckMessageNode",
 | 
			
		||||
        "name": "Has testData",
 | 
			
		||||
        "debugSettings": null,
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "messageNames": [
 | 
			
		||||
            "testData",
 | 
			
		||||
            "testDataCf"
 | 
			
		||||
          ],
 | 
			
		||||
          "metadataNames": [],
 | 
			
		||||
          "checkAllKeys": false
 | 
			
		||||
        },
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "",
 | 
			
		||||
          "layoutX": 928,
 | 
			
		||||
          "layoutY": 266
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.filter.TbCheckMessageNode",
 | 
			
		||||
        "name": "Has testData",
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "configurationVersion": 0,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "messageNames": [
 | 
			
		||||
            "testData"
 | 
			
		||||
          ],
 | 
			
		||||
          "metadataNames": [],
 | 
			
		||||
          "checkAllKeys": true
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": null,
 | 
			
		||||
          "layoutX": 1203,
 | 
			
		||||
          "layoutY": 327
 | 
			
		||||
        },
 | 
			
		||||
        "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
 | 
			
		||||
        "name": "Save Timeseries with TTL",
 | 
			
		||||
        "debugSettings": {
 | 
			
		||||
          "failuresEnabled": true,
 | 
			
		||||
          "allEnabled": false,
 | 
			
		||||
          "allEnabledUntil": 1742305233839
 | 
			
		||||
        },
 | 
			
		||||
        "singletonMode": false,
 | 
			
		||||
        "queueName": null,
 | 
			
		||||
        "configurationVersion": 1,
 | 
			
		||||
        "configuration": {
 | 
			
		||||
          "defaultTTL": 180,
 | 
			
		||||
          "useServerTs": false,
 | 
			
		||||
          "processingSettings": {
 | 
			
		||||
            "type": "ON_EVERY_MESSAGE"
 | 
			
		||||
          }
 | 
			
		||||
          },
 | 
			
		||||
          "defaultTTL": 60,
 | 
			
		||||
          "useServerTs": null
 | 
			
		||||
        },
 | 
			
		||||
        "externalId": null
 | 
			
		||||
        "additionalInfo": {
 | 
			
		||||
          "description": "",
 | 
			
		||||
          "layoutX": 1203,
 | 
			
		||||
          "layoutY": 327
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "connections": [
 | 
			
		||||
@ -352,23 +342,13 @@
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 2,
 | 
			
		||||
        "toIndex": 16,
 | 
			
		||||
        "toIndex": 13,
 | 
			
		||||
        "type": "Post telemetry"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 6,
 | 
			
		||||
        "toIndex": 2,
 | 
			
		||||
        "type": "False"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 6,
 | 
			
		||||
        "toIndex": 7,
 | 
			
		||||
        "type": "True"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 7,
 | 
			
		||||
        "toIndex": 2,
 | 
			
		||||
        "type": "False"
 | 
			
		||||
        "type": "Success"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 7,
 | 
			
		||||
@ -378,54 +358,39 @@
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 8,
 | 
			
		||||
        "toIndex": 2,
 | 
			
		||||
        "type": "Success"
 | 
			
		||||
        "type": "True"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 9,
 | 
			
		||||
        "toIndex": 10,
 | 
			
		||||
        "toIndex": 6,
 | 
			
		||||
        "type": "Success"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 10,
 | 
			
		||||
        "toIndex": 11,
 | 
			
		||||
        "type": "True"
 | 
			
		||||
        "type": "Success"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 11,
 | 
			
		||||
        "toIndex": 6,
 | 
			
		||||
        "type": "True"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 12,
 | 
			
		||||
        "toIndex": 9,
 | 
			
		||||
        "toIndex": 12,
 | 
			
		||||
        "type": "Success"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 13,
 | 
			
		||||
        "toIndex": 14,
 | 
			
		||||
        "type": "Success"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 14,
 | 
			
		||||
        "toIndex": 15,
 | 
			
		||||
        "type": "Success"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 16,
 | 
			
		||||
        "toIndex": 0,
 | 
			
		||||
        "type": "False"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 16,
 | 
			
		||||
        "toIndex": 17,
 | 
			
		||||
        "fromIndex": 13,
 | 
			
		||||
        "toIndex": 14,
 | 
			
		||||
        "type": "True"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "fromIndex": 17,
 | 
			
		||||
        "toIndex": 13,
 | 
			
		||||
        "fromIndex": 14,
 | 
			
		||||
        "toIndex": 10,
 | 
			
		||||
        "type": "Success"
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "ruleChainConnections": null
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user