Merge branch 'master' into fix-edge-zombie-consumer-cleanup
This commit is contained in:
		
						commit
						2966ec77fc
					
				
							
								
								
									
										151
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								README.md
									
									
									
									
									
								
							@ -1,43 +1,146 @@
 | 
			
		||||
# ThingsBoard 
 | 
			
		||||
[](https://builds.thingsboard.io/viewType.html?buildTypeId=ThingsBoard_Build&guest=1)
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
ThingsBoard is an open-source IoT platform for data collection, processing, visualization, and device management.
 | 
			
		||||
<div align="center">
 | 
			
		||||
 | 
			
		||||
<img src="./img/logo.png?raw=true" width="100" height="100">
 | 
			
		||||
# Open-source IoT platform for data collection, processing, visualization, and device management.
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
<br>
 | 
			
		||||
<div align="center">
 | 
			
		||||
 
 | 
			
		||||
💡 [Get started](https://thingsboard.io/docs/getting-started-guides/helloworld/) • 🌐 [Website](https://thingsboard.io/) • 📚 [Documentation](https://thingsboard.io/docs/) • 📔 [Blog](https://thingsboard.io/blog/) • ▶️ [Live demo](https://demo.thingsboard.io/signup) • 🔗 [LinkedIn](https://www.linkedin.com/company/thingsboard/posts/?feedView=all)
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
ThingsBoard documentation is hosted on [thingsboard.io](https://thingsboard.io/docs).
 | 
			
		||||
## 🚀 Installation options
 | 
			
		||||
 | 
			
		||||
## IoT use cases
 | 
			
		||||
* Install ThingsBoard [On-premise](https://thingsboard.io/docs/user-guide/install/installation-options/?ceInstallType=onPremise)
 | 
			
		||||
* Try [ThingsBoard Cloud](https://thingsboard.io/installations/)
 | 
			
		||||
* or [Use our Live demo](https://demo.thingsboard.io/signup)
 | 
			
		||||
 | 
			
		||||
[**Smart energy**](https://thingsboard.io/smart-energy/)
 | 
			
		||||
[](https://thingsboard.io/smart-energy/)
 | 
			
		||||
## 💡 Getting started with ThingsBoard
 | 
			
		||||
 | 
			
		||||
[**SCADA Swimming pool**](https://thingsboard.io/use-cases/scada/)
 | 
			
		||||
[](https://thingsboard.io/use-cases/scada/)
 | 
			
		||||
Check out our [Getting Started guide](https://thingsboard.io/docs/getting-started-guides/helloworld/) or [watch the video](https://www.youtube.com/watch?v=80L0ubQLXsc) to learn the basics of ThingsBoard and create your first dashboard! You will learn to:
 | 
			
		||||
 | 
			
		||||
[**Fleet tracking**](https://thingsboard.io/fleet-tracking/)
 | 
			
		||||
[](https://thingsboard.io/fleet-tracking/)
 | 
			
		||||
* Connect devices to ThingsBoard
 | 
			
		||||
* Push data from devices to ThingsBoard
 | 
			
		||||
* Build real-time dashboards
 | 
			
		||||
* Create a Customer and assign the dashboard with them.
 | 
			
		||||
* Define thresholds and trigger alarms
 | 
			
		||||
* Set up notifications via email, SMS, mobile apps, or integrate with third-party services.
 | 
			
		||||
 | 
			
		||||
[**Smart farming**](https://thingsboard.io/smart-farming/)
 | 
			
		||||
[](https://thingsboard.io/smart-farming/)
 | 
			
		||||
## ✨ Features
 | 
			
		||||
 | 
			
		||||
[**IoT Rule Engine**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
 | 
			
		||||
[](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
 | 
			
		||||
<table>
 | 
			
		||||
  <tr>
 | 
			
		||||
    <td width="50%" valign="top">
 | 
			
		||||
      <br>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <img src="https://github.com/user-attachments/assets/255cca4f-b111-44e8-99ea-0af55f8e3681" alt="Provision and manage devices and assets" width="378" />
 | 
			
		||||
        <h3>Provision and manage <br> devices and assets</h3>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <p>Provision, monitor and control your IoT entities in secure way using rich server-side APIs. Define relations between your devices, assets, customers or any other entities.</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <a href="https://thingsboard.io/docs/user-guide/entities-and-relations/">Read more 🡪</a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <br>
 | 
			
		||||
    </td>
 | 
			
		||||
    <td width="50%" valign="top">
 | 
			
		||||
      <br>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <img src="https://github.com/user-attachments/assets/24b41d10-150a-42dd-ab1a-32ac9b5978c1" alt="Collect and visualize your data" width="378" />
 | 
			
		||||
        <h3>Collect and visualize <br> your data</h3>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <p>Collect and store telemetry data in scalable and fault-tolerant way. Visualize your data with built-in or custom widgets and flexible dashboards. Share dashboards with your customers.</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <a href="https://thingsboard.io/iot-data-visualization/">Read more 🡪</a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <br>
 | 
			
		||||
    </td>
 | 
			
		||||
  </tr>
 | 
			
		||||
  <tr>
 | 
			
		||||
    <td width="50%" valign="top">
 | 
			
		||||
      <br>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <img src="https://github.com/user-attachments/assets/6f2a6dd2-7b33-4d17-8b92-d1f995adda2c" alt="SCADA Dashboards" width="378" />
 | 
			
		||||
        <h3>SCADA Dashboards</h3>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <p>Monitor and control your industrial processes in real time with SCADA. Use SCADA symbols on dashboards to create and manage any workflow, offering full flexibility to design and oversee operations according to your requirements.</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <a href="https://thingsboard.io/use-cases/scada/">Read more 🡪</a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <br>
 | 
			
		||||
    </td>
 | 
			
		||||
    <td width="50%">
 | 
			
		||||
      <br>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <img src="https://github.com/user-attachments/assets/c23dcc9b-aeba-40ef-9973-49b953fc1257" alt="Process and React" width="378" />
 | 
			
		||||
        <h3>Process and React</h3>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <p>Define data processing rule chains. Transform and normalize your device data. Raise alarms on incoming telemetry events, attribute updates, device inactivity and user actions.<br></p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <br>
 | 
			
		||||
      <div align="center">
 | 
			
		||||
        <a href="https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/">Read more 🡪</a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <br>
 | 
			
		||||
    </td>
 | 
			
		||||
  </tr>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
## ⚙️ Powerful IoT Rule Engine
 | 
			
		||||
 | 
			
		||||
ThingsBoard allows you to create complex [Rule Chains](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/) to process data from your devices and match your application specific use cases.
 | 
			
		||||
 | 
			
		||||
[](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
 | 
			
		||||
 | 
			
		||||
<div align="center">
 | 
			
		||||
 | 
			
		||||
[**Read more about Rule Engine 🡪**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
## 📦 Real-Time IoT Dashboards
 | 
			
		||||
 | 
			
		||||
ThingsBoard is a scalable, user-friendly, and device-agnostic IoT platform that speeds up time-to-market with powerful built-in solution templates. It enables data collection and analysis from any devices, saving resources on routine tasks and letting you focus on your solution’s unique aspects. See more our Use Cases [here](https://thingsboard.io/iot-use-cases/).
 | 
			
		||||
 | 
			
		||||
[**Smart energy**](https://thingsboard.io/use-cases/smart-energy/)
 | 
			
		||||
 | 
			
		||||
[](https://thingsboard.io/use-cases/smart-energy/)
 | 
			
		||||
 | 
			
		||||
[**SCADA swimming pool**](https://thingsboard.io/use-cases/scada/)
 | 
			
		||||
 | 
			
		||||
[](https://thingsboard.io/use-cases/scada/)
 | 
			
		||||
 | 
			
		||||
[**Fleet tracking**](https://thingsboard.io/use-cases/fleet-tracking/)
 | 
			
		||||
 | 
			
		||||
[](https://thingsboard.io/use-cases/fleet-tracking/)
 | 
			
		||||
 | 
			
		||||
[**Smart farming**](https://thingsboard.io/use-cases/smart-farming/)
 | 
			
		||||
 | 
			
		||||
[](https://thingsboard.io/use-cases/smart-farming/)
 | 
			
		||||
 | 
			
		||||
[**Smart metering**](https://thingsboard.io/smart-metering/)
 | 
			
		||||
[](https://thingsboard.io/smart-metering/)
 | 
			
		||||
 | 
			
		||||
## Getting Started
 | 
			
		||||
[](https://thingsboard.io/smart-metering/)
 | 
			
		||||
 | 
			
		||||
Collect and Visualize your IoT data in minutes by following this [guide](https://thingsboard.io/docs/getting-started-guides/helloworld/).
 | 
			
		||||
<div align="center">
 | 
			
		||||
 | 
			
		||||
## Support
 | 
			
		||||
[**Check more of our use cases 🡪**](https://thingsboard.io/iot-use-cases/)
 | 
			
		||||
 | 
			
		||||
 - [Stackoverflow](http://stackoverflow.com/questions/tagged/thingsboard)
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
## Licenses
 | 
			
		||||
## 🫶 Support
 | 
			
		||||
 | 
			
		||||
This project is released under [Apache 2.0 License](./LICENSE).
 | 
			
		||||
To get support, please visit our [GitHub issues page](https://github.com/thingsboard/thingsboard/issues)
 | 
			
		||||
 | 
			
		||||
## 📄 Licenses
 | 
			
		||||
 | 
			
		||||
This project is released under [Apache 2.0 License](./LICENSE)
 | 
			
		||||
 | 
			
		||||
@ -14,33 +14,3 @@
 | 
			
		||||
-- limitations under the License.
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
-- UPDATE OTA PACKAGE EXTERNAL ID START
 | 
			
		||||
 | 
			
		||||
ALTER TABLE ota_package
 | 
			
		||||
    ADD COLUMN IF NOT EXISTS external_id uuid;
 | 
			
		||||
 | 
			
		||||
DO
 | 
			
		||||
$$
 | 
			
		||||
    BEGIN
 | 
			
		||||
        IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'ota_package_external_id_unq_key') THEN
 | 
			
		||||
            ALTER TABLE ota_package ADD CONSTRAINT ota_package_external_id_unq_key UNIQUE (tenant_id, external_id);
 | 
			
		||||
        END IF;
 | 
			
		||||
    END;
 | 
			
		||||
$$;
 | 
			
		||||
 | 
			
		||||
-- UPDATE OTA PACKAGE EXTERNAL ID END
 | 
			
		||||
 | 
			
		||||
-- DROP INDEXES THAT DUPLICATE UNIQUE CONSTRAINT START
 | 
			
		||||
 | 
			
		||||
DROP INDEX IF EXISTS idx_device_external_id;
 | 
			
		||||
DROP INDEX IF EXISTS idx_device_profile_external_id;
 | 
			
		||||
DROP INDEX IF EXISTS idx_asset_external_id;
 | 
			
		||||
DROP INDEX IF EXISTS idx_entity_view_external_id;
 | 
			
		||||
DROP INDEX IF EXISTS idx_rule_chain_external_id;
 | 
			
		||||
DROP INDEX IF EXISTS idx_dashboard_external_id;
 | 
			
		||||
DROP INDEX IF EXISTS idx_customer_external_id;
 | 
			
		||||
DROP INDEX IF EXISTS idx_widgets_bundle_external_id;
 | 
			
		||||
 | 
			
		||||
-- DROP INDEXES THAT DUPLICATE UNIQUE CONSTRAINT END
 | 
			
		||||
 | 
			
		||||
ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS title varchar(255);
 | 
			
		||||
@ -114,9 +114,6 @@ public class AppActor extends ContextAwareActor {
 | 
			
		||||
                ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
 | 
			
		||||
                break;
 | 
			
		||||
            case CF_CACHE_INIT_MSG:
 | 
			
		||||
            case CF_INIT_PROFILE_ENTITY_MSG:
 | 
			
		||||
            case CF_INIT_MSG:
 | 
			
		||||
            case CF_LINK_INIT_MSG:
 | 
			
		||||
            case CF_STATE_RESTORE_MSG:
 | 
			
		||||
                //TODO: use priority from the message body. For example, messages about CF lifecycle are important and Device lifecycle are not.
 | 
			
		||||
                //      same for the Linked telemetry.
 | 
			
		||||
 | 
			
		||||
@ -24,9 +24,6 @@ import org.thingsboard.server.common.msg.TbActorStopReason;
 | 
			
		||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -70,15 +67,6 @@ public class CalculatedFieldManagerActor extends AbstractCalculatedFieldActor {
 | 
			
		||||
            case CF_CACHE_INIT_MSG:
 | 
			
		||||
                processor.onCacheInitMsg((CalculatedFieldCacheInitMsg) msg);
 | 
			
		||||
                break;
 | 
			
		||||
            case CF_INIT_PROFILE_ENTITY_MSG:
 | 
			
		||||
                processor.onProfileEntityMsg((CalculatedFieldInitProfileEntityMsg) msg);
 | 
			
		||||
                break;
 | 
			
		||||
            case CF_INIT_MSG:
 | 
			
		||||
                processor.onFieldInitMsg((CalculatedFieldInitMsg) msg);
 | 
			
		||||
                break;
 | 
			
		||||
            case CF_LINK_INIT_MSG:
 | 
			
		||||
                processor.onLinkInitMsg((CalculatedFieldLinkInitMsg) msg);
 | 
			
		||||
                break;
 | 
			
		||||
            case CF_STATE_RESTORE_MSG:
 | 
			
		||||
                processor.onStateRestoreMsg((CalculatedFieldStateRestoreMsg) msg);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
@ -35,9 +35,6 @@ import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.page.PageDataIterable;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldCacheInitMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldEntityLifecycleMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitProfileEntityMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldPartitionChangeMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.queue.ServiceType;
 | 
			
		||||
@ -120,37 +117,6 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
 | 
			
		||||
        msg.getCallback().onSuccess();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onProfileEntityMsg(CalculatedFieldInitProfileEntityMsg msg) {
 | 
			
		||||
        log.debug("[{}] Processing profile entity message.", msg.getTenantId().getId());
 | 
			
		||||
        entityProfileCache.add(msg.getProfileEntityId(), msg.getEntityId());
 | 
			
		||||
        msg.getCallback().onSuccess();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onFieldInitMsg(CalculatedFieldInitMsg msg) throws CalculatedFieldException {
 | 
			
		||||
        log.debug("[{}] Processing CF init message.", msg.getCf().getId());
 | 
			
		||||
        var cf = msg.getCf();
 | 
			
		||||
        var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
 | 
			
		||||
        try {
 | 
			
		||||
            cfCtx.init();
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(cf.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build();
 | 
			
		||||
        }
 | 
			
		||||
        calculatedFields.put(cf.getId(), cfCtx);
 | 
			
		||||
        // We use copy on write lists to safely pass the reference to another actor for the iteration.
 | 
			
		||||
        // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
 | 
			
		||||
        entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
 | 
			
		||||
        msg.getCallback().onSuccess();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onLinkInitMsg(CalculatedFieldLinkInitMsg msg) {
 | 
			
		||||
        log.debug("[{}] Processing CF link init message for entity [{}].", msg.getLink().getCalculatedFieldId(), msg.getLink().getEntityId());
 | 
			
		||||
        var link = msg.getLink();
 | 
			
		||||
        // We use copy on write lists to safely pass the reference to another actor for the iteration.
 | 
			
		||||
        // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
 | 
			
		||||
        entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link);
 | 
			
		||||
        msg.getCallback().onSuccess();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void onStateRestoreMsg(CalculatedFieldStateRestoreMsg msg) {
 | 
			
		||||
        var cfId = msg.getId().cfId();
 | 
			
		||||
        var calculatedField = calculatedFields.get(cfId);
 | 
			
		||||
@ -566,20 +532,37 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
 | 
			
		||||
        cfs.forEach(cf -> {
 | 
			
		||||
            log.trace("Processing calculated field record: {}", cf);
 | 
			
		||||
            try {
 | 
			
		||||
                onFieldInitMsg(new CalculatedFieldInitMsg(cf.getTenantId(), cf));
 | 
			
		||||
                initCalculatedField(cf);
 | 
			
		||||
            } catch (CalculatedFieldException e) {
 | 
			
		||||
                log.error("Failed to process calculated field record: {}", cf, e);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        calculatedFields.values().forEach(cf -> {
 | 
			
		||||
            entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cf);
 | 
			
		||||
        });
 | 
			
		||||
        PageDataIterable<CalculatedFieldLink> cfls = new PageDataIterable<>(pageLink -> cfDaoService.findAllCalculatedFieldLinksByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize());
 | 
			
		||||
        cfls.forEach(link -> {
 | 
			
		||||
            onLinkInitMsg(new CalculatedFieldLinkInitMsg(link.getTenantId(), link));
 | 
			
		||||
            log.trace("Processing calculated field link record: {}", link);
 | 
			
		||||
            initCalculatedFieldLink(link);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initCalculatedField(CalculatedField cf) throws CalculatedFieldException {
 | 
			
		||||
        var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
 | 
			
		||||
        try {
 | 
			
		||||
            cfCtx.init();
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw CalculatedFieldException.builder().ctx(cfCtx).eventEntity(cf.getEntityId()).cause(e).errorMessage("Failed to initialize CF context").build();
 | 
			
		||||
        }
 | 
			
		||||
        calculatedFields.put(cf.getId(), cfCtx);
 | 
			
		||||
        // We use copy on write lists to safely pass the reference to another actor for the iteration.
 | 
			
		||||
        // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
 | 
			
		||||
        entityIdCalculatedFields.computeIfAbsent(cf.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(cfCtx);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initCalculatedFieldLink(CalculatedFieldLink link) {
 | 
			
		||||
        // We use copy on write lists to safely pass the reference to another actor for the iteration.
 | 
			
		||||
        // Alternative approach would be to use any list but avoid modifications to the list (change the complete map value instead)
 | 
			
		||||
        entityIdCalculatedFieldLinks.computeIfAbsent(link.getEntityId(), id -> new CopyOnWriteArrayList<>()).add(link);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void initEntityProfileCache() {
 | 
			
		||||
        PageDataIterable<ProfileEntityIdInfo> deviceIdInfos = new PageDataIterable<>(pageLink -> deviceService.findProfileEntityIdInfosByTenantId(tenantId, pageLink), cfSettings.getInitTenantFetchPackSize());
 | 
			
		||||
        for (ProfileEntityIdInfo idInfo : deviceIdInfos) {
 | 
			
		||||
 | 
			
		||||
@ -180,9 +180,6 @@ public class TenantActor extends RuleChainManagerActor {
 | 
			
		||||
                onRuleChainMsg((RuleChainAwareMsg) msg);
 | 
			
		||||
                break;
 | 
			
		||||
            case CF_CACHE_INIT_MSG:
 | 
			
		||||
            case CF_INIT_PROFILE_ENTITY_MSG:
 | 
			
		||||
            case CF_INIT_MSG:
 | 
			
		||||
            case CF_LINK_INIT_MSG:
 | 
			
		||||
            case CF_STATE_RESTORE_MSG:
 | 
			
		||||
            case CF_PARTITIONS_CHANGE_MSG:
 | 
			
		||||
                onToCalculatedFieldSystemActorMsg((ToCalculatedFieldSystemMsg) msg, true);
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import io.swagger.v3.oas.annotations.Parameter;
 | 
			
		||||
import jakarta.annotation.Nullable;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.http.HttpStatus;
 | 
			
		||||
import org.springframework.http.ResponseEntity;
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
@ -60,7 +61,10 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_
 | 
			
		||||
@RequestMapping(TbUrlConstants.RULE_ENGINE_URL_PREFIX)
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RuleEngineController extends BaseController {
 | 
			
		||||
    public static final int DEFAULT_TIMEOUT = 10000;
 | 
			
		||||
 | 
			
		||||
    @Value("${server.rest.rule_engine.response_timeout:10000}")
 | 
			
		||||
    public int defaultResponseTimeout;
 | 
			
		||||
 | 
			
		||||
    private static final String MSG_DESCRIPTION_PREFIX = "Creates the Message with type 'REST_API_REQUEST' and payload taken from the request body. ";
 | 
			
		||||
    private static final String MSG_DESCRIPTION = "This method allows you to extend the regular platform API with the power of Rule Engine. You may use default and custom rule nodes to handle the message. " +
 | 
			
		||||
            "The generated message contains two important metadata fields:\n\n" +
 | 
			
		||||
@ -85,7 +89,7 @@ public class RuleEngineController extends BaseController {
 | 
			
		||||
    public DeferredResult<ResponseEntity> handleRuleEngineRequest(
 | 
			
		||||
            @Parameter(description = "A JSON value representing the message.", required = true)
 | 
			
		||||
            @RequestBody String requestBody) throws ThingsboardException {
 | 
			
		||||
        return handleRuleEngineRequest(null, null, null, DEFAULT_TIMEOUT, requestBody);
 | 
			
		||||
        return handleRuleEngineRequest(null, null, null, defaultResponseTimeout, requestBody);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Push entity message to the rule engine (handleRuleEngineRequest)",
 | 
			
		||||
@ -104,7 +108,7 @@ public class RuleEngineController extends BaseController {
 | 
			
		||||
            @PathVariable("entityId") String entityIdStr,
 | 
			
		||||
            @Parameter(description = "A JSON value representing the message.", required = true)
 | 
			
		||||
            @RequestBody String requestBody) throws ThingsboardException {
 | 
			
		||||
        return handleRuleEngineRequest(entityType, entityIdStr, null, DEFAULT_TIMEOUT, requestBody);
 | 
			
		||||
        return handleRuleEngineRequest(entityType, entityIdStr, null, defaultResponseTimeout, requestBody);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Push entity message with timeout to the rule engine (handleRuleEngineRequest)",
 | 
			
		||||
 | 
			
		||||
@ -19,11 +19,9 @@ import lombok.Getter;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.util.ConcurrentReferenceHashMap;
 | 
			
		||||
import org.thingsboard.script.api.tbel.TbelInvokeService;
 | 
			
		||||
import org.thingsboard.server.actors.ActorSystemContext;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
 | 
			
		||||
@ -31,8 +29,6 @@ import org.thingsboard.server.common.data.id.CalculatedFieldId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.page.PageDataIterable;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldInitMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.cf.CalculatedFieldLinkInitMsg;
 | 
			
		||||
import org.thingsboard.server.dao.cf.CalculatedFieldService;
 | 
			
		||||
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
 | 
			
		||||
import org.thingsboard.server.queue.util.AfterStartUp;
 | 
			
		||||
@ -56,8 +52,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
 | 
			
		||||
    private final CalculatedFieldService calculatedFieldService;
 | 
			
		||||
    private final TbelInvokeService tbelInvokeService;
 | 
			
		||||
    private final ApiLimitService apiLimitService;
 | 
			
		||||
    @Lazy
 | 
			
		||||
    private final ActorSystemContext actorSystemContext;
 | 
			
		||||
 | 
			
		||||
    private final ConcurrentMap<CalculatedFieldId, CalculatedField> calculatedFields = new ConcurrentHashMap<>();
 | 
			
		||||
    private final ConcurrentMap<EntityId, List<CalculatedField>> entityIdCalculatedFields = new ConcurrentHashMap<>();
 | 
			
		||||
@ -75,7 +69,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
 | 
			
		||||
        cfs.forEach(cf -> {
 | 
			
		||||
            if (cf != null) {
 | 
			
		||||
                calculatedFields.putIfAbsent(cf.getId(), cf);
 | 
			
		||||
                actorSystemContext.tell(new CalculatedFieldInitMsg(cf.getTenantId(), cf));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        calculatedFields.values().forEach(cf -> {
 | 
			
		||||
@ -84,7 +77,6 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
 | 
			
		||||
        PageDataIterable<CalculatedFieldLink> cfls = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFieldLinks, initFetchPackSize);
 | 
			
		||||
        cfls.forEach(link -> {
 | 
			
		||||
            calculatedFieldLinks.computeIfAbsent(link.getCalculatedFieldId(), id -> new CopyOnWriteArrayList<>()).add(link);
 | 
			
		||||
            actorSystemContext.tell(new CalculatedFieldLinkInitMsg(link.getTenantId(), link));
 | 
			
		||||
        });
 | 
			
		||||
        calculatedFieldLinks.values().stream()
 | 
			
		||||
                .flatMap(List::stream)
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ public class DefaultDatabaseSchemaSettingsService implements DatabaseSchemaSetti
 | 
			
		||||
 | 
			
		||||
    // This list should include all versions which are compatible for the upgrade.
 | 
			
		||||
    // The compatibility cycle usually breaks when we have some scripts written in Java that may not work after new release.
 | 
			
		||||
    private static final List<String> SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.1.0");
 | 
			
		||||
    private static final List<String> SUPPORTED_VERSIONS_FOR_UPGRADE = List.of("4.2.0");
 | 
			
		||||
 | 
			
		||||
    private final ProjectInfo projectInfo;
 | 
			
		||||
    private final JdbcTemplate jdbcTemplate;
 | 
			
		||||
 | 
			
		||||
@ -15,8 +15,6 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.service.install.update;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.common.util.concurrent.Futures;
 | 
			
		||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
			
		||||
@ -24,12 +22,9 @@ import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.context.annotation.Profile;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
 | 
			
		||||
import org.thingsboard.server.common.data.id.RuleNodeId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.page.PageDataIterable;
 | 
			
		||||
import org.thingsboard.server.common.data.query.DynamicValue;
 | 
			
		||||
import org.thingsboard.server.common.data.query.FilterPredicateValue;
 | 
			
		||||
import org.thingsboard.server.dao.rule.RuleChainService;
 | 
			
		||||
import org.thingsboard.server.service.component.ComponentDiscoveryService;
 | 
			
		||||
import org.thingsboard.server.service.component.RuleNodeClassInfo;
 | 
			
		||||
@ -129,60 +124,6 @@ public class DefaultDataUpdateService implements DataUpdateService {
 | 
			
		||||
        return ruleNodeIds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    boolean convertDeviceProfileForVersion330(JsonNode profileData) {
 | 
			
		||||
        boolean isUpdated = false;
 | 
			
		||||
        if (profileData.has("alarms") && !profileData.get("alarms").isNull()) {
 | 
			
		||||
            JsonNode alarms = profileData.get("alarms");
 | 
			
		||||
            for (JsonNode alarm : alarms) {
 | 
			
		||||
                if (alarm.has("createRules")) {
 | 
			
		||||
                    JsonNode createRules = alarm.get("createRules");
 | 
			
		||||
                    for (AlarmSeverity severity : AlarmSeverity.values()) {
 | 
			
		||||
                        if (createRules.has(severity.name())) {
 | 
			
		||||
                            JsonNode spec = createRules.get(severity.name()).get("condition").get("spec");
 | 
			
		||||
                            if (convertDeviceProfileAlarmRulesForVersion330(spec)) {
 | 
			
		||||
                                isUpdated = true;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (alarm.has("clearRule") && !alarm.get("clearRule").isNull()) {
 | 
			
		||||
                    JsonNode spec = alarm.get("clearRule").get("condition").get("spec");
 | 
			
		||||
                    if (convertDeviceProfileAlarmRulesForVersion330(spec)) {
 | 
			
		||||
                        isUpdated = true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return isUpdated;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) {
 | 
			
		||||
        if (spec != null) {
 | 
			
		||||
            if (spec.has("type") && spec.get("type").asText().equals("DURATION")) {
 | 
			
		||||
                if (spec.has("value")) {
 | 
			
		||||
                    long value = spec.get("value").asLong();
 | 
			
		||||
                    var predicate = new FilterPredicateValue<>(
 | 
			
		||||
                            value, null, new DynamicValue<>(null, null, false)
 | 
			
		||||
                    );
 | 
			
		||||
                    ((ObjectNode) spec).remove("value");
 | 
			
		||||
                    ((ObjectNode) spec).putPOJO("predicate", predicate);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (spec.has("type") && spec.get("type").asText().equals("REPEATING")) {
 | 
			
		||||
                if (spec.has("count")) {
 | 
			
		||||
                    int count = spec.get("count").asInt();
 | 
			
		||||
                    var predicate = new FilterPredicateValue<>(
 | 
			
		||||
                            count, null, new DynamicValue<>(null, null, false)
 | 
			
		||||
                    );
 | 
			
		||||
                    ((ObjectNode) spec).remove("count");
 | 
			
		||||
                    ((ObjectNode) spec).putPOJO("predicate", predicate);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean getEnv(String name, boolean defaultValue) {
 | 
			
		||||
        String env = System.getenv(name);
 | 
			
		||||
        if (env == null) {
 | 
			
		||||
 | 
			
		||||
@ -100,6 +100,9 @@ server:
 | 
			
		||||
    rate_limits:
 | 
			
		||||
      # Limit that prohibits resetting the password for the user too often. The value of the rate limit. By default, no more than 5 requests per hour
 | 
			
		||||
      reset_password_per_user: "${RESET_PASSWORD_PER_USER_RATE_LIMIT_CONFIGURATION:5:3600}"
 | 
			
		||||
    rule_engine:
 | 
			
		||||
      # Default timeout for waiting response of REST API request to Rule Engine in milliseconds
 | 
			
		||||
      response_timeout: "${DEFAULT_RULE_ENGINE_RESPONSE_TIMEOUT:10000}"
 | 
			
		||||
 | 
			
		||||
# Application info parameters
 | 
			
		||||
app:
 | 
			
		||||
 | 
			
		||||
@ -953,7 +953,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
 | 
			
		||||
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter);
 | 
			
		||||
        EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter, keyFilters);
 | 
			
		||||
        countByQueryAndCheck(countQuery, 97);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,95 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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.server.service.install.update;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.core.JsonProcessingException;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import org.junit.jupiter.api.BeforeEach;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
import org.springframework.boot.test.context.SpringBootTest;
 | 
			
		||||
import org.springframework.boot.test.mock.mockito.MockBean;
 | 
			
		||||
import org.springframework.test.context.ActiveProfiles;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
import static org.mockito.ArgumentMatchers.any;
 | 
			
		||||
import static org.mockito.BDDMockito.willCallRealMethod;
 | 
			
		||||
 | 
			
		||||
@ActiveProfiles("install")
 | 
			
		||||
@SpringBootTest(classes = DefaultDataUpdateService.class)
 | 
			
		||||
class DefaultDataUpdateServiceTest {
 | 
			
		||||
 | 
			
		||||
    @MockBean
 | 
			
		||||
    DefaultDataUpdateService service;
 | 
			
		||||
 | 
			
		||||
    @BeforeEach
 | 
			
		||||
    void setUp() {
 | 
			
		||||
        willCallRealMethod().given(service).convertDeviceProfileAlarmRulesForVersion330(any());
 | 
			
		||||
        willCallRealMethod().given(service).convertDeviceProfileForVersion330(any());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    JsonNode readFromResource(String resourceName) throws IOException {
 | 
			
		||||
        return JacksonUtil.OBJECT_MAPPER.readTree(this.getClass().getClassLoader().getResourceAsStream(resourceName));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void convertDeviceProfileAlarmRulesForVersion330FirstRun() throws IOException {
 | 
			
		||||
        JsonNode spec = readFromResource("update/330/device_profile_001_in.json");
 | 
			
		||||
        JsonNode expected = readFromResource("update/330/device_profile_001_out.json");
 | 
			
		||||
 | 
			
		||||
        assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isTrue();
 | 
			
		||||
        assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature <Click to see difference>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void convertDeviceProfileAlarmRulesForVersion330SecondRun() throws IOException {
 | 
			
		||||
        JsonNode spec = readFromResource("update/330/device_profile_001_out.json");
 | 
			
		||||
        JsonNode expected = readFromResource("update/330/device_profile_001_out.json");
 | 
			
		||||
 | 
			
		||||
        assertThat(service.convertDeviceProfileForVersion330(spec.get("profileData"))).isFalse();
 | 
			
		||||
        assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString()); // use IDE feature <Click to see difference>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void convertDeviceProfileAlarmRulesForVersion330EmptyJson() throws JsonProcessingException {
 | 
			
		||||
        JsonNode spec = JacksonUtil.toJsonNode("{ }");
 | 
			
		||||
        JsonNode expected = JacksonUtil.toJsonNode("{ }");
 | 
			
		||||
 | 
			
		||||
        assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
 | 
			
		||||
        assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void convertDeviceProfileAlarmRulesForVersion330AlarmNodeNull() throws JsonProcessingException {
 | 
			
		||||
        JsonNode spec = JacksonUtil.toJsonNode("{ \"alarms\" : null }");
 | 
			
		||||
        JsonNode expected = JacksonUtil.toJsonNode("{ \"alarms\" : null }");
 | 
			
		||||
 | 
			
		||||
        assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
 | 
			
		||||
        assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    void convertDeviceProfileAlarmRulesForVersion330NoAlarmNode() throws JsonProcessingException {
 | 
			
		||||
        JsonNode spec = JacksonUtil.toJsonNode("{ \"configuration\": { \"type\": \"DEFAULT\" } }");
 | 
			
		||||
        JsonNode expected = JacksonUtil.toJsonNode("{ \"configuration\": { \"type\": \"DEFAULT\" } }");
 | 
			
		||||
 | 
			
		||||
        assertThat(service.convertDeviceProfileForVersion330(spec)).isFalse();
 | 
			
		||||
        assertThat(spec.toPrettyString()).isEqualTo(expected.toPrettyString());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -137,9 +137,6 @@ public enum MsgType {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    CF_CACHE_INIT_MSG, // Sent to init caches for CF actor;
 | 
			
		||||
    CF_INIT_PROFILE_ENTITY_MSG, // Sent to init profile entities cache;
 | 
			
		||||
    CF_INIT_MSG, // Sent to init particular calculated field;
 | 
			
		||||
    CF_LINK_INIT_MSG, // Sent to init particular calculated field;
 | 
			
		||||
    CF_STATE_RESTORE_MSG, // Sent to restore particular calculated field entity state;
 | 
			
		||||
    CF_PARTITIONS_CHANGE_MSG, // Sent when cluster event occures;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,34 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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.server.common.msg.cf;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.msg.MsgType;
 | 
			
		||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class CalculatedFieldInitMsg implements ToCalculatedFieldSystemMsg {
 | 
			
		||||
 | 
			
		||||
    private final TenantId tenantId;
 | 
			
		||||
    private final CalculatedField cf;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MsgType getMsgType() {
 | 
			
		||||
        return MsgType.CF_INIT_MSG;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,36 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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.server.common.msg.cf;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.msg.MsgType;
 | 
			
		||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class CalculatedFieldInitProfileEntityMsg implements ToCalculatedFieldSystemMsg {
 | 
			
		||||
 | 
			
		||||
    private final TenantId tenantId;
 | 
			
		||||
    private final EntityId profileEntityId;
 | 
			
		||||
    private final EntityId entityId;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MsgType getMsgType() {
 | 
			
		||||
        return MsgType.CF_INIT_PROFILE_ENTITY_MSG;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,34 +0,0 @@
 | 
			
		||||
/**
 | 
			
		||||
 * 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.server.common.msg.cf;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.msg.MsgType;
 | 
			
		||||
import org.thingsboard.server.common.msg.ToCalculatedFieldSystemMsg;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class CalculatedFieldLinkInitMsg implements ToCalculatedFieldSystemMsg {
 | 
			
		||||
 | 
			
		||||
    private final TenantId tenantId;
 | 
			
		||||
    private final CalculatedFieldLink link;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MsgType getMsgType() {
 | 
			
		||||
        return MsgType.CF_LINK_INIT_MSG;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -31,7 +31,7 @@ import java.util.concurrent.TimeUnit;
 | 
			
		||||
public class AbstractRedisClusterContainer {
 | 
			
		||||
 | 
			
		||||
    static final String NODES = "127.0.0.1:6371,127.0.0.1:6372,127.0.0.1:6373,127.0.0.1:6374,127.0.0.1:6375,127.0.0.1:6376";
 | 
			
		||||
    static final String IMAGE = "bitnami/valkey-cluster:8.0";
 | 
			
		||||
    static final String IMAGE = "bitnamilegacy/valkey-cluster:8.0";
 | 
			
		||||
    static final Map<String,String> ENVS = Map.of(
 | 
			
		||||
            "VALKEY_CLUSTER_ANNOUNCE_IP", "127.0.0.1",
 | 
			
		||||
            "VALKEY_CLUSTER_DYNAMIC_IPS", "no",
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ import java.util.List;
 | 
			
		||||
public class AbstractRedisContainer {
 | 
			
		||||
 | 
			
		||||
    @ClassRule(order = 0)
 | 
			
		||||
    public static GenericContainer redis = new GenericContainer("bitnami/valkey:8.0")
 | 
			
		||||
    public static GenericContainer redis = new GenericContainer("bitnamilegacy/valkey:8.0")
 | 
			
		||||
            .withEnv("ALLOW_EMPTY_PASSWORD","yes")
 | 
			
		||||
            .withLogConsumer(s -> log.warn(((OutputFrame) s).getUtf8String().trim()))
 | 
			
		||||
            .withExposedPorts(6379);
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
public class RedisJUnit5Test {
 | 
			
		||||
 | 
			
		||||
    @Container
 | 
			
		||||
    private static final GenericContainer REDIS = new GenericContainer("bitnami/valkey:8.0")
 | 
			
		||||
    private static final GenericContainer REDIS = new GenericContainer("bitnamilegacy/valkey:8.0")
 | 
			
		||||
            .withEnv("ALLOW_EMPTY_PASSWORD","yes")
 | 
			
		||||
            .withLogConsumer(s -> log.error(((OutputFrame) s).getUtf8String().trim()))
 | 
			
		||||
            .withExposedPorts(6379);
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@
 | 
			
		||||
services:
 | 
			
		||||
  kafka:
 | 
			
		||||
    restart: always
 | 
			
		||||
    image: "bitnami/kafka:4.0"
 | 
			
		||||
    image: "bitnamilegacy/kafka:4.0"
 | 
			
		||||
    ports:
 | 
			
		||||
      - "9092:9092"
 | 
			
		||||
    env_file:
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ services:
 | 
			
		||||
# Valkey cluster
 | 
			
		||||
# The latest version of Valkey compatible with ThingsBoard is 8.0
 | 
			
		||||
  valkey-node-0:
 | 
			
		||||
    image: bitnami/valkey-cluster:8.0
 | 
			
		||||
    image: bitnamilegacy/valkey-cluster:8.0
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-cluster-data-0:/bitnami/valkey/data
 | 
			
		||||
    environment:
 | 
			
		||||
@ -26,7 +26,7 @@ services:
 | 
			
		||||
      - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
 | 
			
		||||
 | 
			
		||||
  valkey-node-1:
 | 
			
		||||
    image: bitnami/valkey-cluster:8.0
 | 
			
		||||
    image: bitnamilegacy/valkey-cluster:8.0
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-cluster-data-1:/bitnami/valkey/data
 | 
			
		||||
    depends_on:
 | 
			
		||||
@ -36,7 +36,7 @@ services:
 | 
			
		||||
      - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
 | 
			
		||||
 | 
			
		||||
  valkey-node-2:
 | 
			
		||||
    image: bitnami/valkey-cluster:8.0
 | 
			
		||||
    image: bitnamilegacy/valkey-cluster:8.0
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-cluster-data-2:/bitnami/valkey/data
 | 
			
		||||
    depends_on:
 | 
			
		||||
@ -46,7 +46,7 @@ services:
 | 
			
		||||
      - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
 | 
			
		||||
 | 
			
		||||
  valkey-node-3:
 | 
			
		||||
    image: bitnami/valkey-cluster:8.0
 | 
			
		||||
    image: bitnamilegacy/valkey-cluster:8.0
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-cluster-data-3:/bitnami/valkey/data
 | 
			
		||||
    depends_on:
 | 
			
		||||
@ -56,7 +56,7 @@ services:
 | 
			
		||||
      - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
 | 
			
		||||
 | 
			
		||||
  valkey-node-4:
 | 
			
		||||
    image: bitnami/valkey-cluster:8.0
 | 
			
		||||
    image: bitnamilegacy/valkey-cluster:8.0
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-cluster-data-4:/bitnami/valkey/data
 | 
			
		||||
    depends_on:
 | 
			
		||||
@ -66,7 +66,7 @@ services:
 | 
			
		||||
      - 'VALKEY_NODES=valkey-node-0 valkey-node-1 valkey-node-2 valkey-node-3 valkey-node-4 valkey-node-5'
 | 
			
		||||
 | 
			
		||||
  valkey-node-5:
 | 
			
		||||
    image: bitnami/valkey-cluster:8.0
 | 
			
		||||
    image: bitnamilegacy/valkey-cluster:8.0
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-cluster-data-5:/bitnami/valkey/data
 | 
			
		||||
    depends_on:
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ services:
 | 
			
		||||
  # Valkey sentinel
 | 
			
		||||
  # The latest version of Valkey compatible with ThingsBoard is 8.0
 | 
			
		||||
  valkey-primary:
 | 
			
		||||
    image: 'bitnami/valkey:8.0'
 | 
			
		||||
    image: 'bitnamilegacy/valkey:8.0'
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-sentinel-data-primary:/bitnami/valkey/data
 | 
			
		||||
    environment:
 | 
			
		||||
@ -26,7 +26,7 @@ services:
 | 
			
		||||
      - 'VALKEY_PASSWORD=thingsboard'
 | 
			
		||||
 | 
			
		||||
  valkey-replica:
 | 
			
		||||
    image: 'bitnami/valkey:8.0'
 | 
			
		||||
    image: 'bitnamilegacy/valkey:8.0'
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-sentinel-data-replica:/bitnami/valkey/data
 | 
			
		||||
    environment:
 | 
			
		||||
@ -38,7 +38,7 @@ services:
 | 
			
		||||
      - valkey-primary
 | 
			
		||||
 | 
			
		||||
  valkey-sentinel:
 | 
			
		||||
    image: 'bitnami/valkey-sentinel:8.0'
 | 
			
		||||
    image: 'bitnamilegacy/valkey-sentinel:8.0'
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./tb-node/valkey-sentinel-data-sentinel:/bitnami/valkey/data
 | 
			
		||||
    environment:
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ services:
 | 
			
		||||
# The latest version of Valkey compatible with ThingsBoard is 8.0
 | 
			
		||||
  valkey:
 | 
			
		||||
    restart: always
 | 
			
		||||
    image: bitnami/valkey:8.0
 | 
			
		||||
    image: bitnamilegacy/valkey:8.0
 | 
			
		||||
    environment:
 | 
			
		||||
      # ALLOW_EMPTY_PASSWORD is recommended only for development.
 | 
			
		||||
      ALLOW_EMPTY_PASSWORD: "yes"
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ services:
 | 
			
		||||
# The latest version of Valkey compatible with ThingsBoard is 8.0
 | 
			
		||||
  valkey:
 | 
			
		||||
    restart: always
 | 
			
		||||
    image: bitnami/valkey:8.0
 | 
			
		||||
    image: bitnamilegacy/valkey:8.0
 | 
			
		||||
    environment:
 | 
			
		||||
      # ALLOW_EMPTY_PASSWORD is recommended only for development.
 | 
			
		||||
      - 'ALLOW_EMPTY_PASSWORD=yes'
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,9 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
 | 
			
		||||
    if (!this.aiConfigForm.get('systemPrompt').value) {
 | 
			
		||||
      delete config.systemPrompt;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.aiConfigForm.get('responseFormat.type').value !== ResponseFormat.JSON_SCHEMA) {
 | 
			
		||||
      delete config.responseFormat.schema;
 | 
			
		||||
    }
 | 
			
		||||
    return deepTrim(config);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -88,10 +91,10 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
 | 
			
		||||
        if (this.aiConfigForm.get('responseFormat.type').value !== ResponseFormat.TEXT) {
 | 
			
		||||
          this.aiConfigForm.get('responseFormat.type').patchValue(ResponseFormat.TEXT, {emitEvent: true});
 | 
			
		||||
        }
 | 
			
		||||
        this.aiConfigForm.get('responseFormat.type').disable();
 | 
			
		||||
        this.aiConfigForm.get('responseFormat.type').disable({emitEvent: false});
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.aiConfigForm.get('responseFormat.type').enable();
 | 
			
		||||
      this.aiConfigForm.get('responseFormat.type').enable({emitEvent: false});
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user