Browse Source
# Conflicts: # application/src/main/data/json/system/widget_bundles/home_page_widgets.json # application/src/main/data/upgrade/basic/schema_update.sql # application/src/main/java/org/thingsboard/server/service/entitiy/cf/TbCalculatedFieldService.java # dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java # ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table-config.ts # ui-ngx/src/app/modules/home/components/calculated-fields/calculated-fields-table.component.ts # ui-ngx/src/app/modules/home/components/home-components.module.ts # ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts # ui-ngx/src/app/shared/components/entity/entity-select.component.tspull/15547/head
258 changed files with 27370 additions and 484 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,143 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.controller; |
|||
|
|||
import io.swagger.v3.oas.annotations.Hidden; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import org.springframework.web.bind.annotation.DeleteMapping; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import jakarta.servlet.http.HttpServletRequest; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.iot_hub.DeviceInstalledItemDescriptor; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
|||
import org.thingsboard.server.dao.device.DeviceConnectivityService; |
|||
import org.thingsboard.server.dao.iot_hub.IotHubInstalledItemService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.security.system.SystemSecurityService; |
|||
import org.thingsboard.server.service.iot_hub.InstallItemVersionResult; |
|||
import org.thingsboard.server.service.iot_hub.UpdateItemVersionResult; |
|||
import org.thingsboard.server.service.iot_hub.IotHubService; |
|||
|
|||
@Hidden |
|||
@RestController |
|||
@TbCoreComponent |
|||
@RequestMapping("/api/iot-hub") |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class IotHubController extends BaseController { |
|||
|
|||
private final IotHubService iotHubService; |
|||
private final IotHubInstalledItemService iotHubInstalledItemService; |
|||
private final DeviceConnectivityService deviceConnectivityService; |
|||
private final SystemSecurityService systemSecurityService; |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@PostMapping("/versions/{versionId}/install") |
|||
@ResponseBody |
|||
public InstallItemVersionResult installItemVersion(@PathVariable String versionId, |
|||
@RequestBody(required = false) JsonNode data, |
|||
HttpServletRequest request) throws ThingsboardException { |
|||
return iotHubService.installItemVersion(getCurrentUser(), versionId, data, request); |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@PostMapping("/device/register") |
|||
@ResponseBody |
|||
public InstallItemVersionResult registerDeviceInstall( |
|||
@RequestParam String versionId, |
|||
@RequestBody JsonNode body) throws ThingsboardException { |
|||
DeviceInstalledItemDescriptor descriptor = JacksonUtil.treeToValue(body, DeviceInstalledItemDescriptor.class); |
|||
return iotHubService.registerDeviceInstall(getCurrentUser(), versionId, descriptor); |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@PostMapping("/installedItems/{installedItemId}/update/{versionId}") |
|||
@ResponseBody |
|||
public UpdateItemVersionResult updateItemVersion(@PathVariable UUID installedItemId, |
|||
@PathVariable String versionId, |
|||
@RequestParam(required = false, defaultValue = "false") boolean force, |
|||
HttpServletRequest request) throws ThingsboardException { |
|||
return iotHubService.updateItemVersion(getCurrentUser(), new IotHubInstalledItemId(installedItemId), versionId, force, request); |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/installedItems") |
|||
@ResponseBody |
|||
public PageData<IotHubInstalledItem> getInstalledItems(@RequestParam int pageSize, |
|||
@RequestParam int page, |
|||
@RequestParam(required = false) String textSearch, |
|||
@RequestParam(required = false) String sortProperty, |
|||
@RequestParam(required = false) String sortOrder, |
|||
@RequestParam(required = false) List<String> itemTypes, |
|||
@RequestParam(required = false) UUID itemId) throws ThingsboardException { |
|||
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
|||
return iotHubInstalledItemService.findByTenantId(getTenantId(), itemTypes, itemId, pageLink); |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/installedItems/count") |
|||
@ResponseBody |
|||
public long getInstalledItemsCount(@RequestParam(required = false) String itemType) throws ThingsboardException { |
|||
return iotHubInstalledItemService.countByTenantId(getTenantId(), itemType); |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/installedItems/itemIds") |
|||
@ResponseBody |
|||
public List<UUID> getInstalledItemIds() throws ThingsboardException { |
|||
return iotHubInstalledItemService.findInstalledItemIdsByTenantId(getTenantId()); |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/installedItems/counts") |
|||
@ResponseBody |
|||
public Map<UUID, Long> getInstalledItemCounts(@RequestParam String itemType) throws ThingsboardException { |
|||
return iotHubInstalledItemService.findInstalledItemCounts(getTenantId(), itemType); |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@DeleteMapping("/installedItems/{installedItemId}") |
|||
@ResponseBody |
|||
public void deleteInstalledItem(@PathVariable UUID installedItemId) throws ThingsboardException { |
|||
iotHubService.deleteInstalledItem(getCurrentUser(), new IotHubInstalledItemId(installedItemId)); |
|||
} |
|||
|
|||
@PreAuthorize("hasAuthority('TENANT_ADMIN')") |
|||
@GetMapping("/connectivity") |
|||
@ResponseBody |
|||
public JsonNode getConnectivitySettings(HttpServletRequest request) throws Exception { |
|||
String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); |
|||
return deviceConnectivityService.getConnectivityInfo(baseUrl); |
|||
} |
|||
} |
|||
@ -0,0 +1,607 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.iot_hub; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import jakarta.servlet.http.HttpServletRequest; |
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.common.data.Dashboard; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
import org.thingsboard.server.common.data.id.DeviceId; |
|||
import org.thingsboard.server.common.data.id.DeviceProfileId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.iot_hub.CalculatedFieldInstalledItemDescriptor; |
|||
import org.thingsboard.server.common.data.iot_hub.DashboardInstalledItemDescriptor; |
|||
import org.thingsboard.server.common.data.iot_hub.DeviceInstalledItemDescriptor; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemDescriptor; |
|||
import org.thingsboard.server.common.data.iot_hub.RuleChainInstalledItemDescriptor; |
|||
import org.thingsboard.server.common.data.iot_hub.SolutionTemplateInstalledItemDescriptor; |
|||
import org.thingsboard.server.common.data.iot_hub.WidgetInstalledItemDescriptor; |
|||
import org.thingsboard.server.service.solutions.SolutionService; |
|||
import org.thingsboard.server.service.solutions.data.solution.SolutionInstallResponse; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.common.data.rule.NodeConnectionInfo; |
|||
import org.thingsboard.server.common.data.rule.RuleChainMetaData; |
|||
import org.thingsboard.server.common.data.rule.RuleNode; |
|||
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; |
|||
import org.thingsboard.server.dao.cf.CalculatedFieldService; |
|||
import org.thingsboard.server.dao.dashboard.DashboardService; |
|||
import org.thingsboard.server.dao.device.DeviceProfileService; |
|||
import org.thingsboard.server.dao.device.DeviceService; |
|||
import org.thingsboard.server.dao.iot_hub.IotHubInstalledItemService; |
|||
import org.thingsboard.server.dao.rule.RuleChainService; |
|||
import org.thingsboard.server.dao.widget.WidgetTypeService; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
import org.thingsboard.server.service.entitiy.cf.TbCalculatedFieldService; |
|||
import org.thingsboard.server.service.entitiy.dashboard.TbDashboardService; |
|||
import org.thingsboard.server.service.entitiy.device.TbDeviceService; |
|||
import org.thingsboard.server.service.entitiy.device.profile.TbDeviceProfileService; |
|||
import org.thingsboard.server.service.entitiy.widgets.type.TbWidgetTypeService; |
|||
import org.thingsboard.server.service.rule.TbRuleChainService; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
|
|||
import java.nio.charset.StandardCharsets; |
|||
import java.security.MessageDigest; |
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.HexFormat; |
|||
import java.util.List; |
|||
import java.util.UUID; |
|||
|
|||
@Service |
|||
@TbCoreComponent |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
public class DefaultIotHubService implements IotHubService { |
|||
|
|||
private final IotHubRestClient iotHubRestClient; |
|||
private final TbWidgetTypeService tbWidgetTypeService; |
|||
private final TbDashboardService tbDashboardService; |
|||
private final TbCalculatedFieldService tbCalculatedFieldService; |
|||
private final RuleChainService ruleChainService; |
|||
private final TbRuleChainService tbRuleChainService; |
|||
private final TbDeviceProfileService tbDeviceProfileService; |
|||
private final IotHubInstalledItemService iotHubInstalledItemService; |
|||
private final WidgetTypeService widgetTypeService; |
|||
private final DashboardService dashboardService; |
|||
private final CalculatedFieldService calculatedFieldService; |
|||
private final DeviceProfileService deviceProfileService; |
|||
private final DeviceService deviceService; |
|||
private final TbDeviceService tbDeviceService; |
|||
private final SolutionService solutionService; |
|||
|
|||
@Override |
|||
public InstallItemVersionResult installItemVersion(SecurityUser user, String versionId, JsonNode data, HttpServletRequest request) { |
|||
TenantId tenantId = user.getTenantId(); |
|||
log.info("[{}] Installing IoT Hub item version: {}", tenantId, versionId); |
|||
|
|||
try { |
|||
JsonNode versionInfo = iotHubRestClient.getVersionInfo(versionId); |
|||
String itemType = versionInfo.get("type").asText(); |
|||
String itemName = versionInfo.get("name").asText(); |
|||
UUID itemId = UUID.fromString(versionInfo.get("itemId").asText()); |
|||
String version = versionInfo.get("version").asText(); |
|||
log.debug("[{}] Fetched version info: {} (type: {})", tenantId, itemName, itemType); |
|||
|
|||
byte[] fileData = iotHubRestClient.getVersionFileData(versionId); |
|||
log.debug("[{}] Fetched file data, size: {} bytes", tenantId, fileData != null ? fileData.length : 0); |
|||
|
|||
IotHubInstalledItemDescriptor descriptor = switch (itemType) { |
|||
case "WIDGET" -> installWidget(user, tenantId, fileData); |
|||
case "DASHBOARD" -> installDashboard(user, tenantId, fileData); |
|||
case "CALCULATED_FIELD" -> installCalculatedField(user, tenantId, fileData, data); |
|||
case "RULE_CHAIN" -> installRuleChain(tenantId, fileData); |
|||
case "DEVICE" -> installDeviceProfile(user, tenantId, fileData); |
|||
case "SOLUTION_TEMPLATE" -> installSolution(user, tenantId, fileData, request); |
|||
default -> throw new IllegalArgumentException("Unsupported IoT Hub item type: " + itemType); |
|||
}; |
|||
|
|||
IotHubInstalledItem installedItem = new IotHubInstalledItem(); |
|||
installedItem.setTenantId(tenantId); |
|||
installedItem.setItemId(itemId); |
|||
installedItem.setItemVersionId(UUID.fromString(versionId)); |
|||
installedItem.setItemName(itemName); |
|||
installedItem.setItemType(itemType); |
|||
installedItem.setVersion(version); |
|||
installedItem.setDescriptor(descriptor); |
|||
iotHubInstalledItemService.save(tenantId, installedItem); |
|||
|
|||
iotHubRestClient.reportVersionInstalled(versionId); |
|||
log.info("[{}] Successfully installed IoT Hub item version: {} (type: {})", tenantId, itemName, itemType); |
|||
|
|||
return InstallItemVersionResult.success(descriptor); |
|||
} catch (Exception e) { |
|||
log.error("[{}] Failed to install IoT Hub item version: {}", tenantId, versionId, e); |
|||
return InstallItemVersionResult.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
private WidgetInstalledItemDescriptor installWidget(SecurityUser user, TenantId tenantId, byte[] fileData) throws Exception { |
|||
WidgetTypeDetails widgetTypeDetails; |
|||
try { |
|||
widgetTypeDetails = JacksonUtil.fromString(new String(fileData), WidgetTypeDetails.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse widget data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
widgetTypeDetails.setId(null); |
|||
widgetTypeDetails.setTenantId(tenantId); |
|||
WidgetTypeDetails saved = tbWidgetTypeService.save(widgetTypeDetails, false, user); |
|||
log.debug("[{}] Widget installed: {}", tenantId, saved.getName()); |
|||
WidgetInstalledItemDescriptor descriptor = new WidgetInstalledItemDescriptor(); |
|||
descriptor.setWidgetTypeId(saved.getId()); |
|||
return descriptor; |
|||
} |
|||
|
|||
private DashboardInstalledItemDescriptor installDashboard(SecurityUser user, TenantId tenantId, byte[] fileData) throws Exception { |
|||
Dashboard dashboard; |
|||
try { |
|||
dashboard = JacksonUtil.fromString(new String(fileData), Dashboard.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse dashboard data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
dashboard.setId(null); |
|||
dashboard.setTenantId(tenantId); |
|||
Dashboard saved = tbDashboardService.save(dashboard, user); |
|||
log.debug("[{}] Dashboard installed: {}", tenantId, saved.getTitle()); |
|||
DashboardInstalledItemDescriptor descriptor = new DashboardInstalledItemDescriptor(); |
|||
descriptor.setDashboardId(saved.getId()); |
|||
return descriptor; |
|||
} |
|||
|
|||
private CalculatedFieldInstalledItemDescriptor installCalculatedField(SecurityUser user, TenantId tenantId, byte[] fileData, JsonNode data) throws Exception { |
|||
CalculatedField calculatedField; |
|||
try { |
|||
calculatedField = JacksonUtil.fromString(new String(fileData), CalculatedField.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse calculated field data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
calculatedField.setId(null); |
|||
calculatedField.setTenantId(tenantId); |
|||
if (data != null && data.has("entityId")) { |
|||
EntityId entityId = JacksonUtil.treeToValue(data.get("entityId"), EntityId.class); |
|||
calculatedField.setEntityId(entityId); |
|||
} |
|||
CalculatedField saved = tbCalculatedFieldService.save(calculatedField, user); |
|||
log.debug("[{}] Calculated field installed: {}", tenantId, saved.getName()); |
|||
CalculatedFieldInstalledItemDescriptor descriptor = new CalculatedFieldInstalledItemDescriptor(); |
|||
descriptor.setCalculatedFieldId(saved.getId()); |
|||
descriptor.setEntityId(saved.getEntityId()); |
|||
return descriptor; |
|||
} |
|||
|
|||
private RuleChainInstalledItemDescriptor installRuleChain(TenantId tenantId, byte[] fileData) throws Exception { |
|||
JsonNode json = JacksonUtil.toJsonNode(new String(fileData)); |
|||
|
|||
RuleChain ruleChain; |
|||
try { |
|||
ruleChain = JacksonUtil.fromString(json.get("ruleChain").toString(), RuleChain.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse rule chain: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
|
|||
RuleChainMetaData metadata; |
|||
try { |
|||
metadata = JacksonUtil.fromString(json.get("metadata").toString(), RuleChainMetaData.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse rule chain metadata: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
|
|||
ruleChain.setId(null); |
|||
ruleChain.setTenantId(tenantId); |
|||
RuleChain savedRuleChain = ruleChainService.saveRuleChain(ruleChain); |
|||
|
|||
metadata.setRuleChainId(savedRuleChain.getId()); |
|||
metadata.setVersion(savedRuleChain.getVersion()); |
|||
ruleChainService.saveRuleChainMetaData(tenantId, metadata, tbRuleChainService::updateRuleNodeConfiguration); |
|||
|
|||
log.debug("[{}] Rule chain installed: {}", tenantId, savedRuleChain.getName()); |
|||
RuleChainInstalledItemDescriptor descriptor = new RuleChainInstalledItemDescriptor(); |
|||
descriptor.setRuleChainId(savedRuleChain.getId()); |
|||
return descriptor; |
|||
} |
|||
|
|||
private DeviceInstalledItemDescriptor installDeviceProfile(SecurityUser user, TenantId tenantId, byte[] fileData) throws Exception { |
|||
DeviceProfile deviceProfile; |
|||
try { |
|||
deviceProfile = JacksonUtil.fromString(new String(fileData), DeviceProfile.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse device profile data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
deviceProfile.setId(null); |
|||
deviceProfile.setTenantId(tenantId); |
|||
tbDeviceProfileService.save(deviceProfile, user); |
|||
log.debug("[{}] Device profile installed: {}", tenantId, deviceProfile.getName()); |
|||
return new DeviceInstalledItemDescriptor(); |
|||
} |
|||
|
|||
private SolutionTemplateInstalledItemDescriptor installSolution(SecurityUser user, TenantId tenantId, byte[] fileData, HttpServletRequest request) throws Exception { |
|||
SolutionInstallResponse response = solutionService.installSolution(user, tenantId, fileData, request); |
|||
if (!response.isSuccess()) { |
|||
throw new RuntimeException(response.getDetails()); |
|||
} |
|||
SolutionTemplateInstalledItemDescriptor descriptor = new SolutionTemplateInstalledItemDescriptor(); |
|||
descriptor.setCreatedEntityIds(response.getCreatedEntityIds()); |
|||
descriptor.setTenantTelemetryKeys(response.getTenantTelemetryKeys()); |
|||
descriptor.setTenantAttributeKeys(response.getTenantAttributeKeys()); |
|||
descriptor.setDashboardId(response.getDashboardId()); |
|||
descriptor.setPublicId(response.getPublicId()); |
|||
descriptor.setMainDashboardPublic(response.isMainDashboardPublic()); |
|||
descriptor.setDetails(response.getDetails()); |
|||
return descriptor; |
|||
} |
|||
|
|||
@Override |
|||
public UpdateItemVersionResult updateItemVersion(SecurityUser user, IotHubInstalledItemId installedItemId, String versionId, boolean force, HttpServletRequest request) { |
|||
TenantId tenantId = user.getTenantId(); |
|||
log.info("[{}] Updating IoT Hub installed item {} to version: {}", tenantId, installedItemId, versionId); |
|||
|
|||
try { |
|||
IotHubInstalledItem installedItem = iotHubInstalledItemService.findById(tenantId, installedItemId); |
|||
if (installedItem == null) { |
|||
throw new IllegalArgumentException("Installed item not found"); |
|||
} |
|||
|
|||
String itemType = installedItem.getItemType(); |
|||
IotHubInstalledItemDescriptor descriptor = installedItem.getDescriptor(); |
|||
|
|||
// Skip checksum validation for solution templates
|
|||
if (!"SOLUTION_TEMPLATE".equals(itemType)) { |
|||
JsonNode installedVersionInfo = iotHubRestClient.getVersionInfo(installedItem.getItemVersionId().toString()); |
|||
String installedChecksum = installedVersionInfo.has("checksum") ? installedVersionInfo.get("checksum").asText() : null; |
|||
log.info("[{}] Installed version info: name={}, version={}, checksum={}", tenantId, installedItem.getItemName(), installedItem.getVersion(), installedChecksum); |
|||
|
|||
if (!force) { |
|||
String entityChecksum = calculateEntityChecksum(tenantId, installedItem); |
|||
boolean entityModified = installedChecksum != null && !installedChecksum.equals(entityChecksum); |
|||
log.info("[{}] Entity checksum: {}, modified: {}", tenantId, entityChecksum, entityModified); |
|||
|
|||
if (entityModified) { |
|||
return UpdateItemVersionResult.entityModified(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
JsonNode versionInfo = iotHubRestClient.getVersionInfo(versionId); |
|||
String itemName = versionInfo.get("name").asText(); |
|||
String version = versionInfo.get("version").asText(); |
|||
|
|||
byte[] fileData = iotHubRestClient.getVersionFileData(versionId); |
|||
log.info("[{}] Fetched update file data, size: {} bytes", tenantId, fileData != null ? fileData.length : 0); |
|||
|
|||
switch (itemType) { |
|||
case "WIDGET" -> updateWidget(user, tenantId, (WidgetInstalledItemDescriptor) descriptor, fileData); |
|||
case "DASHBOARD" -> updateDashboard(user, tenantId, (DashboardInstalledItemDescriptor) descriptor, fileData); |
|||
case "CALCULATED_FIELD" -> updateCalculatedField(user, tenantId, (CalculatedFieldInstalledItemDescriptor) descriptor, fileData); |
|||
case "RULE_CHAIN" -> updateRuleChain(tenantId, (RuleChainInstalledItemDescriptor) descriptor, fileData); |
|||
case "DEVICE" -> { |
|||
DeviceInstalledItemDescriptor dd = (DeviceInstalledItemDescriptor) descriptor; |
|||
deleteDevicePackageEntities(tenantId, dd.getCreatedEntityIds(), user); |
|||
dd.setCreatedEntityIds(null); |
|||
dd.setDashboardId(null); |
|||
} |
|||
case "SOLUTION_TEMPLATE" -> { |
|||
SolutionTemplateInstalledItemDescriptor stDescriptor = (SolutionTemplateInstalledItemDescriptor) descriptor; |
|||
solutionService.deleteSolution(tenantId, stDescriptor, user); |
|||
SolutionInstallResponse response = solutionService.installSolution(user, tenantId, fileData, request); |
|||
if (!response.isSuccess()) { |
|||
throw new RuntimeException(response.getDetails()); |
|||
} |
|||
stDescriptor.setCreatedEntityIds(response.getCreatedEntityIds()); |
|||
stDescriptor.setDashboardId(response.getDashboardId()); |
|||
stDescriptor.setPublicId(response.getPublicId()); |
|||
stDescriptor.setMainDashboardPublic(response.isMainDashboardPublic()); |
|||
stDescriptor.setDetails(response.getDetails()); |
|||
} |
|||
default -> throw new IllegalArgumentException("Unsupported IoT Hub item type: " + itemType); |
|||
} |
|||
|
|||
installedItem.setItemVersionId(UUID.fromString(versionId)); |
|||
installedItem.setItemName(itemName); |
|||
installedItem.setVersion(version); |
|||
iotHubInstalledItemService.save(tenantId, installedItem); |
|||
|
|||
log.info("[{}] Successfully updated IoT Hub item {} to version: {}", tenantId, itemName, version); |
|||
|
|||
return UpdateItemVersionResult.success(installedItem.getDescriptor()); |
|||
} catch (Exception e) { |
|||
log.error("[{}] Failed to update IoT Hub installed item {} to version: {}", tenantId, installedItemId, versionId, e); |
|||
return UpdateItemVersionResult.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
private void updateWidget(SecurityUser user, TenantId tenantId, WidgetInstalledItemDescriptor descriptor, byte[] fileData) throws Exception { |
|||
WidgetTypeDetails newWidgetType; |
|||
try { |
|||
newWidgetType = JacksonUtil.fromString(new String(fileData), WidgetTypeDetails.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse widget data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
WidgetTypeDetails existing = widgetTypeService.findWidgetTypeDetailsById(tenantId, descriptor.getWidgetTypeId()); |
|||
if (existing == null) { |
|||
throw new Exception("Widget not found for update"); |
|||
} |
|||
existing.setName(newWidgetType.getName()); |
|||
existing.setDescriptor(newWidgetType.getDescriptor()); |
|||
tbWidgetTypeService.save(existing, false, user); |
|||
} |
|||
|
|||
private void updateDashboard(SecurityUser user, TenantId tenantId, DashboardInstalledItemDescriptor descriptor, byte[] fileData) throws Exception { |
|||
Dashboard newDashboard; |
|||
try { |
|||
newDashboard = JacksonUtil.fromString(new String(fileData), Dashboard.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse dashboard data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
Dashboard existing = dashboardService.findDashboardById(tenantId, descriptor.getDashboardId()); |
|||
if (existing == null) { |
|||
throw new Exception("Dashboard not found for update"); |
|||
} |
|||
existing.setTitle(newDashboard.getTitle()); |
|||
existing.setConfiguration(newDashboard.getConfiguration()); |
|||
tbDashboardService.save(existing, user); |
|||
} |
|||
|
|||
private void updateCalculatedField(SecurityUser user, TenantId tenantId, CalculatedFieldInstalledItemDescriptor descriptor, byte[] fileData) throws Exception { |
|||
CalculatedField newCf; |
|||
try { |
|||
newCf = JacksonUtil.fromString(new String(fileData), CalculatedField.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse calculated field data: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
CalculatedField existing = calculatedFieldService.findById(tenantId, descriptor.getCalculatedFieldId()); |
|||
if (existing == null) { |
|||
throw new Exception("Calculated field not found for update"); |
|||
} |
|||
existing.setName(newCf.getName()); |
|||
existing.setType(newCf.getType()); |
|||
existing.setConfiguration(newCf.getConfiguration()); |
|||
tbCalculatedFieldService.save(existing, user); |
|||
} |
|||
|
|||
private void updateRuleChain(TenantId tenantId, RuleChainInstalledItemDescriptor descriptor, byte[] fileData) throws Exception { |
|||
JsonNode json = JacksonUtil.toJsonNode(new String(fileData)); |
|||
RuleChainMetaData metadata; |
|||
try { |
|||
metadata = JacksonUtil.fromString(json.get("metadata").toString(), RuleChainMetaData.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse rule chain metadata: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
RuleChain existing = ruleChainService.findRuleChainById(tenantId, descriptor.getRuleChainId()); |
|||
if (existing == null) { |
|||
throw new Exception("Rule chain not found for update"); |
|||
} |
|||
RuleChain newRuleChain; |
|||
try { |
|||
newRuleChain = JacksonUtil.fromString(json.get("ruleChain").toString(), RuleChain.class, true); |
|||
} catch (Exception e) { |
|||
throw new Exception("Failed to parse rule chain: " + (e.getCause() != null ? e.getCause().getMessage() : e.getMessage()), e); |
|||
} |
|||
existing.setName(newRuleChain.getName()); |
|||
RuleChain savedRuleChain = ruleChainService.saveRuleChain(existing); |
|||
metadata.setRuleChainId(savedRuleChain.getId()); |
|||
metadata.setVersion(savedRuleChain.getVersion()); |
|||
ruleChainService.saveRuleChainMetaData(tenantId, metadata, tbRuleChainService::updateRuleNodeConfiguration); |
|||
} |
|||
|
|||
private void updateDeviceProfile(SecurityUser user, TenantId tenantId, byte[] fileData) throws Exception { |
|||
// TODO: implement device profile update
|
|||
} |
|||
|
|||
private String calculateEntityChecksum(TenantId tenantId, IotHubInstalledItem installedItem) { |
|||
IotHubInstalledItemDescriptor descriptor = installedItem.getDescriptor(); |
|||
if (descriptor instanceof WidgetInstalledItemDescriptor wd) { |
|||
return calculateWidgetChecksum(tenantId, wd); |
|||
} else if (descriptor instanceof DashboardInstalledItemDescriptor dd) { |
|||
return calculateDashboardChecksum(tenantId, dd); |
|||
} else if (descriptor instanceof CalculatedFieldInstalledItemDescriptor cd) { |
|||
return calculateCalculatedFieldChecksum(tenantId, cd); |
|||
} else if (descriptor instanceof RuleChainInstalledItemDescriptor rd) { |
|||
return calculateRuleChainChecksum(tenantId, rd); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
private String calculateCalculatedFieldChecksum(TenantId tenantId, CalculatedFieldInstalledItemDescriptor descriptor) { |
|||
CalculatedField cf = calculatedFieldService.findById(tenantId, descriptor.getCalculatedFieldId()); |
|||
if (cf == null) { |
|||
return null; |
|||
} |
|||
String content = (cf.getName() != null ? cf.getName() : "") + |
|||
(cf.getType() != null ? cf.getType().name() : "") + |
|||
(cf.getConfiguration() != null ? JacksonUtil.valueToTree(cf.getConfiguration()).toString() : ""); |
|||
return sha256(content); |
|||
} |
|||
|
|||
private String calculateDashboardChecksum(TenantId tenantId, DashboardInstalledItemDescriptor descriptor) { |
|||
Dashboard dashboard = dashboardService.findDashboardById(tenantId, descriptor.getDashboardId()); |
|||
if (dashboard == null) { |
|||
return null; |
|||
} |
|||
String content = (dashboard.getTitle() != null ? dashboard.getTitle() : "") + |
|||
(dashboard.getConfiguration() != null ? dashboard.getConfiguration().toString() : ""); |
|||
return sha256(content); |
|||
} |
|||
|
|||
private String calculateWidgetChecksum(TenantId tenantId, WidgetInstalledItemDescriptor descriptor) { |
|||
WidgetTypeDetails widgetType = widgetTypeService.findWidgetTypeDetailsById(tenantId, descriptor.getWidgetTypeId()); |
|||
if (widgetType == null) { |
|||
return null; |
|||
} |
|||
String content = (widgetType.getFqn() != null ? widgetType.getFqn() : "") + |
|||
(widgetType.getName() != null ? widgetType.getName() : "") + |
|||
(widgetType.getDescriptor() != null ? widgetType.getDescriptor().toString() : ""); |
|||
return sha256(content); |
|||
} |
|||
|
|||
private String calculateRuleChainChecksum(TenantId tenantId, RuleChainInstalledItemDescriptor descriptor) { |
|||
RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, descriptor.getRuleChainId()); |
|||
if (ruleChain == null) { |
|||
return null; |
|||
} |
|||
RuleChainMetaData metadata = ruleChainService.loadRuleChainMetaData(tenantId, descriptor.getRuleChainId()); |
|||
StringBuilder content = new StringBuilder(); |
|||
content.append(ruleChain.getName() != null ? ruleChain.getName() : ""); |
|||
content.append(ruleChain.getType() != null ? ruleChain.getType().name() : ""); |
|||
if (metadata.getNodes() != null) { |
|||
for (RuleNode node : metadata.getNodes()) { |
|||
content.append(node.getType() != null ? node.getType() : ""); |
|||
content.append(node.getName() != null ? node.getName() : ""); |
|||
content.append(node.getConfiguration() != null ? node.getConfiguration().toString() : ""); |
|||
} |
|||
} |
|||
if (metadata.getConnections() != null) { |
|||
for (NodeConnectionInfo conn : metadata.getConnections()) { |
|||
content.append(conn.getFromIndex()); |
|||
content.append(conn.getToIndex()); |
|||
content.append(conn.getType() != null ? conn.getType() : ""); |
|||
} |
|||
} |
|||
return sha256(content.toString()); |
|||
} |
|||
|
|||
private static String sha256(String input) { |
|||
try { |
|||
MessageDigest digest = MessageDigest.getInstance("SHA-256"); |
|||
byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); |
|||
return HexFormat.of().formatHex(hash); |
|||
} catch (Exception e) { |
|||
throw new RuntimeException("Failed to calculate SHA-256", e); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public InstallItemVersionResult registerDeviceInstall(SecurityUser user, String versionId, DeviceInstalledItemDescriptor descriptor) { |
|||
TenantId tenantId = user.getTenantId(); |
|||
log.info("[{}] Registering device package install for version: {}", tenantId, versionId); |
|||
|
|||
try { |
|||
JsonNode versionInfo = iotHubRestClient.getVersionInfo(versionId); |
|||
String itemName = versionInfo.get("name").asText(); |
|||
UUID itemId = UUID.fromString(versionInfo.get("itemId").asText()); |
|||
String version = versionInfo.get("version").asText(); |
|||
|
|||
IotHubInstalledItem installedItem = new IotHubInstalledItem(); |
|||
installedItem.setTenantId(tenantId); |
|||
installedItem.setItemId(itemId); |
|||
installedItem.setItemVersionId(UUID.fromString(versionId)); |
|||
installedItem.setItemName(itemName); |
|||
installedItem.setItemType("DEVICE"); |
|||
installedItem.setVersion(version); |
|||
installedItem.setDescriptor(descriptor); |
|||
iotHubInstalledItemService.save(tenantId, installedItem); |
|||
|
|||
iotHubRestClient.reportVersionInstalled(versionId); |
|||
log.info("[{}] Registered device package install: {} (version {})", tenantId, itemName, version); |
|||
|
|||
return InstallItemVersionResult.success(descriptor); |
|||
} catch (Exception e) { |
|||
log.error("[{}] Failed to register device package install: {}", tenantId, versionId, e); |
|||
return InstallItemVersionResult.error(e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
private void deleteDevicePackageEntities(TenantId tenantId, List<EntityId> createdEntityIds, SecurityUser user) { |
|||
if (createdEntityIds == null || createdEntityIds.isEmpty()) { |
|||
return; |
|||
} |
|||
List<EntityId> reversed = new ArrayList<>(createdEntityIds); |
|||
Collections.reverse(reversed); |
|||
for (EntityId entityId : reversed) { |
|||
try { |
|||
deleteDevicePackageEntity(tenantId, entityId, user); |
|||
} catch (Exception e) { |
|||
log.error("[{}] Failed to delete device package entity: {}", tenantId, entityId, e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void deleteDevicePackageEntity(TenantId tenantId, EntityId entityId, SecurityUser user) { |
|||
switch (entityId.getEntityType()) { |
|||
case DEVICE -> { |
|||
var device = deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())); |
|||
if (device != null) tbDeviceService.delete(device, user); |
|||
} |
|||
case DEVICE_PROFILE -> { |
|||
var profile = deviceProfileService.findDeviceProfileById(tenantId, new DeviceProfileId(entityId.getId())); |
|||
if (profile != null) tbDeviceProfileService.delete(profile, user); |
|||
} |
|||
case DASHBOARD -> { |
|||
var dashboard = dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())); |
|||
if (dashboard != null) tbDashboardService.delete(dashboard, user); |
|||
} |
|||
case RULE_CHAIN -> { |
|||
var ruleChain = ruleChainService.findRuleChainById(tenantId, new RuleChainId(entityId.getId())); |
|||
if (ruleChain != null) tbRuleChainService.delete(ruleChain, user); |
|||
} |
|||
default -> log.warn("[{}] Unsupported entity type for device package delete: {}", tenantId, entityId.getEntityType()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void deleteInstalledItem(SecurityUser user, IotHubInstalledItemId installedItemId) { |
|||
TenantId tenantId = user.getTenantId(); |
|||
IotHubInstalledItem installedItem = iotHubInstalledItemService.findById(tenantId, installedItemId); |
|||
if (installedItem == null) { |
|||
throw new IllegalArgumentException("Installed item not found"); |
|||
} |
|||
|
|||
IotHubInstalledItemDescriptor descriptor = installedItem.getDescriptor(); |
|||
if (descriptor instanceof WidgetInstalledItemDescriptor wd) { |
|||
WidgetTypeDetails widgetType = widgetTypeService.findWidgetTypeDetailsById(tenantId, wd.getWidgetTypeId()); |
|||
if (widgetType != null) { |
|||
tbWidgetTypeService.delete(widgetType, user); |
|||
} |
|||
} else if (descriptor instanceof DashboardInstalledItemDescriptor dd) { |
|||
Dashboard dashboard = dashboardService.findDashboardById(tenantId, dd.getDashboardId()); |
|||
if (dashboard != null) { |
|||
tbDashboardService.delete(dashboard, user); |
|||
} |
|||
} else if (descriptor instanceof CalculatedFieldInstalledItemDescriptor cd) { |
|||
CalculatedField calculatedField = calculatedFieldService.findById(tenantId, cd.getCalculatedFieldId()); |
|||
if (calculatedField != null) { |
|||
tbCalculatedFieldService.delete(calculatedField, user); |
|||
} |
|||
} else if (descriptor instanceof RuleChainInstalledItemDescriptor rd) { |
|||
if (rd.getRuleChainId() != null) { |
|||
RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, rd.getRuleChainId()); |
|||
if (ruleChain != null) { |
|||
tbRuleChainService.delete(ruleChain, user); |
|||
} |
|||
} |
|||
} else if (descriptor instanceof DeviceInstalledItemDescriptor dd) { |
|||
deleteDevicePackageEntities(tenantId, dd.getCreatedEntityIds(), user); |
|||
} else if (descriptor instanceof SolutionTemplateInstalledItemDescriptor st) { |
|||
try { |
|||
solutionService.deleteSolution(tenantId, st, user); |
|||
} catch (ThingsboardException e) { |
|||
log.error("[{}] Failed to delete solution for installed item {}", tenantId, installedItemId, e); |
|||
} |
|||
} |
|||
|
|||
iotHubInstalledItemService.deleteById(tenantId, installedItemId); |
|||
log.info("[{}] Deleted installed IoT Hub item: {}", tenantId, installedItem.getItemName()); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.iot_hub; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemDescriptor; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class InstallItemVersionResult { |
|||
|
|||
private boolean success; |
|||
private String errorMessage; |
|||
private IotHubInstalledItemDescriptor descriptor; |
|||
|
|||
public static InstallItemVersionResult success(IotHubInstalledItemDescriptor descriptor) { |
|||
return new InstallItemVersionResult(true, null, descriptor); |
|||
} |
|||
|
|||
public static InstallItemVersionResult error(String errorMessage) { |
|||
return new InstallItemVersionResult(false, errorMessage, null); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.iot_hub; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.http.ResponseEntity; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.web.client.RestTemplate; |
|||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|||
|
|||
@Component |
|||
@TbCoreComponent |
|||
@Slf4j |
|||
public class IotHubRestClient { |
|||
|
|||
private final RestTemplate restTemplate = new RestTemplate(); |
|||
|
|||
@Value("${iot-hub.base-url:https://iot-hub.thingsboard.io}") |
|||
private String baseUrl; |
|||
|
|||
public JsonNode getVersionInfo(String versionId) { |
|||
String url = baseUrl + "/api/versions/" + versionId; |
|||
log.debug("Fetching IoT Hub version info: {}", url); |
|||
return restTemplate.getForObject(url, JsonNode.class); |
|||
} |
|||
|
|||
public byte[] getVersionFileData(String versionId) { |
|||
String url = baseUrl + "/api/versions/" + versionId + "/fileData"; |
|||
log.debug("Fetching IoT Hub version file data: {}", url); |
|||
ResponseEntity<byte[]> response = restTemplate.getForEntity(url, byte[].class); |
|||
return response.getBody(); |
|||
} |
|||
|
|||
public void reportVersionInstalled(String versionId) { |
|||
String url = baseUrl + "/api/versions/" + versionId + "/install"; |
|||
log.debug("Reporting IoT Hub version installed: {}", url); |
|||
restTemplate.postForObject(url, null, Void.class); |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.iot_hub; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import jakarta.servlet.http.HttpServletRequest; |
|||
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
|||
import org.thingsboard.server.common.data.iot_hub.DeviceInstalledItemDescriptor; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
|
|||
public interface IotHubService { |
|||
|
|||
InstallItemVersionResult installItemVersion(SecurityUser user, String versionId, JsonNode data, HttpServletRequest request); |
|||
|
|||
UpdateItemVersionResult updateItemVersion(SecurityUser user, IotHubInstalledItemId installedItemId, String versionId, boolean force, HttpServletRequest request); |
|||
|
|||
InstallItemVersionResult registerDeviceInstall(SecurityUser user, String versionId, DeviceInstalledItemDescriptor descriptor); |
|||
|
|||
void deleteInstalledItem(SecurityUser user, IotHubInstalledItemId installedItemId); |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.iot_hub; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemDescriptor; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class UpdateItemVersionResult { |
|||
|
|||
private boolean success; |
|||
private boolean entityModified; |
|||
private String errorMessage; |
|||
private IotHubInstalledItemDescriptor descriptor; |
|||
|
|||
public static UpdateItemVersionResult success(IotHubInstalledItemDescriptor descriptor) { |
|||
return new UpdateItemVersionResult(true, false, null, descriptor); |
|||
} |
|||
|
|||
public static UpdateItemVersionResult entityModified() { |
|||
return new UpdateItemVersionResult(false, true, null, null); |
|||
} |
|||
|
|||
public static UpdateItemVersionResult error(String errorMessage) { |
|||
return new UpdateItemVersionResult(false, false, errorMessage, null); |
|||
} |
|||
|
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions; |
|||
|
|||
import jakarta.servlet.http.HttpServletRequest; |
|||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.iot_hub.SolutionTemplateInstalledItemDescriptor; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.solutions.data.solution.SolutionInstallResponse; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface SolutionService { |
|||
|
|||
SolutionInstallResponse installSolution(SecurityUser user, TenantId tenantId, byte[] zipData, HttpServletRequest request) throws Exception; |
|||
|
|||
void deleteSolution(TenantId tenantId, SolutionTemplateInstalledItemDescriptor descriptor, SecurityUser user) throws ThingsboardException; |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data; |
|||
|
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public record CreatedAlarmRuleInfo(EntityId entityId, String entityName, String alarmType, String severities) { |
|||
|
|||
public String getCfPageLink(UUID cfId) { |
|||
return "/alarms/alarm-rules/" + cfId; |
|||
} |
|||
|
|||
public String getEntityPageLink() { |
|||
if (entityId == null) { |
|||
return null; |
|||
} |
|||
return switch (entityId.getEntityType()) { |
|||
case DEVICE_PROFILE -> "/profiles/deviceProfiles/" + entityId.getId(); |
|||
case ASSET_PROFILE -> "/profiles/assetProfiles/" + entityId.getId(); |
|||
default -> null; |
|||
}; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data; |
|||
|
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public record CreatedCalculatedFieldInfo(EntityId entityId, String entityName, String type, String name) { |
|||
|
|||
public String getCfPageLink(UUID cfId) { |
|||
return "/calculatedFields/" + cfId; |
|||
} |
|||
|
|||
public String getEntityPageLink() { |
|||
if (entityId == null) { |
|||
return null; |
|||
} |
|||
return switch (entityId.getEntityType()) { |
|||
case DEVICE_PROFILE -> "/profiles/deviceProfiles/" + entityId.getId(); |
|||
case ASSET_PROFILE -> "/profiles/assetProfiles/" + entityId.getId(); |
|||
default -> null; |
|||
}; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class CreatedEntityInfo { |
|||
|
|||
private String name; |
|||
private EntityType type; |
|||
private String owner; |
|||
|
|||
public String getEntityPageLink(UUID id) { |
|||
return switch (type) { |
|||
case CUSTOMER -> "/customers/" + id; |
|||
case USER -> "/users/" + id; |
|||
case ASSET -> "/entities/assets/" + id; |
|||
case DEVICE -> "/entities/devices/" + id; |
|||
case DEVICE_PROFILE -> "/profiles/deviceProfiles/" + id; |
|||
case ASSET_PROFILE -> "/profiles/assetProfiles/" + id; |
|||
case DASHBOARD -> "/dashboards/" + id; |
|||
case RULE_CHAIN -> "/ruleChains/" + id; |
|||
case EDGE -> "/edgeManagement/instances/" + id; |
|||
default -> null; |
|||
}; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.rule.RuleChainType; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class CreatedRuleChainInfo extends CreatedEntityInfo { |
|||
|
|||
private RuleChainType ruleChainType; |
|||
|
|||
public CreatedRuleChainInfo(String name, RuleChainType ruleChainType, String owner) { |
|||
super(name, EntityType.RULE_CHAIN, owner); |
|||
this.ruleChainType = ruleChainType; |
|||
} |
|||
|
|||
@Override |
|||
public String getEntityPageLink(UUID id) { |
|||
return ruleChainType == RuleChainType.EDGE ? "/edgeManagement/ruleChains/" + id : "/ruleChains/" + id; |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.security.DeviceCredentials; |
|||
|
|||
@Data |
|||
public class DeviceCredentialsInfo { |
|||
|
|||
String name; |
|||
String type; |
|||
DeviceCredentials credentials; |
|||
String customerName; |
|||
boolean gateway; |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.EdgeId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
@Data |
|||
public class EdgeLinkInfo { |
|||
|
|||
private final EdgeId edgeId; |
|||
private final EntityId ownerId; |
|||
|
|||
} |
|||
@ -0,0 +1,209 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.Customer; |
|||
import org.thingsboard.server.common.data.Dashboard; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.data.User; |
|||
import org.thingsboard.server.common.data.asset.Asset; |
|||
import org.thingsboard.server.common.data.asset.AssetProfile; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
import org.thingsboard.server.common.data.edge.Edge; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.rule.RuleChain; |
|||
import org.thingsboard.server.service.solutions.data.definition.AssetDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.AssetProfileDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.CustomerDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.DashboardDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.DeviceDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.EdgeDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.DeviceProfileDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.EntityDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.EntitySearchKey; |
|||
import org.thingsboard.server.service.solutions.data.definition.EmulatorDefinition; |
|||
import org.thingsboard.server.service.solutions.data.definition.RelationDefinition; |
|||
import org.thingsboard.server.service.solutions.data.solution.TenantSolutionTemplateInstructions; |
|||
|
|||
import java.nio.file.Path; |
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.LinkedHashMap; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
@Data |
|||
public class SolutionInstallContext { |
|||
|
|||
private final TenantId tenantId; |
|||
private final Path tempDir; |
|||
private final User user; |
|||
private final TenantSolutionTemplateInstructions solutionInstructions; |
|||
private final List<EntityId> createdEntitiesList = new ArrayList<>(); |
|||
private final Map<String, String> realIds = new HashMap<>(); |
|||
private final Map<EntitySearchKey, EntityId> entityIdMap = new HashMap<>(); |
|||
private final Map<EntityId, List<RelationDefinition>> relationDefinitions = new LinkedHashMap<>(); |
|||
private final List<DashboardLinkInfo> dashboardLinks = new ArrayList<>(); |
|||
|
|||
// For instructions
|
|||
private final Map<String, DeviceCredentialsInfo> createdDevices = new LinkedHashMap<>(); |
|||
private final Map<String, UserCredentialsInfo> createdUsers = new LinkedHashMap<>(); |
|||
private final Map<String, EdgeLinkInfo> createdEdges = new LinkedHashMap<>(); |
|||
private final Map<UUID, CreatedEntityInfo> createdEntities = new LinkedHashMap<>(); |
|||
private final Map<UUID, CreatedAlarmRuleInfo> createdAlarmRules = new LinkedHashMap<>(); |
|||
private final Map<UUID, CreatedCalculatedFieldInfo> createdCalculatedFields = new LinkedHashMap<>(); |
|||
private final String solutionId; |
|||
private final long installTs; |
|||
private long oldestTelemetryTs; |
|||
private Map<String, EmulatorDefinition> deviceEmulators; |
|||
private Map<String, EmulatorDefinition> assetEmulators; |
|||
|
|||
public SolutionInstallContext(TenantId tenantId, String solutionId, Path tempDir, User user, TenantSolutionTemplateInstructions solutionInstructions) { |
|||
this.tenantId = tenantId; |
|||
this.solutionId = solutionId; |
|||
this.tempDir = tempDir; |
|||
this.user = user; |
|||
this.solutionInstructions = solutionInstructions; |
|||
this.installTs = System.currentTimeMillis(); |
|||
put(new EntitySearchKey(tenantId, EntityType.TENANT, null), tenantId); |
|||
} |
|||
|
|||
public void registerReferenceOnly(String referenceId, EntityId entityId) { |
|||
if (!StringUtils.isEmpty(referenceId)) { |
|||
realIds.put(referenceId, entityId.getId().toString()); |
|||
} |
|||
} |
|||
|
|||
public void register(String referenceId, EntityId entityId) { |
|||
registerReferenceOnly(referenceId, entityId); |
|||
register(entityId); |
|||
} |
|||
|
|||
public void register(EntityId entityId) { |
|||
createdEntitiesList.add(entityId); |
|||
} |
|||
|
|||
public void register(String referenceId, RuleChain ruleChain) { |
|||
register(referenceId, ruleChain.getId()); |
|||
createdEntities.put(ruleChain.getUuidId(), new CreatedRuleChainInfo(ruleChain.getName(), ruleChain.getType(), "Tenant")); |
|||
} |
|||
|
|||
public void register(DeviceProfileDefinition definition, DeviceProfile deviceProfile) { |
|||
register(definition.getJsonId(), deviceProfile.getId()); |
|||
createdEntities.put(deviceProfile.getUuidId(), new CreatedEntityInfo(deviceProfile.getName(), EntityType.DEVICE_PROFILE, "Tenant")); |
|||
} |
|||
|
|||
public void register(AssetProfileDefinition definition, AssetProfile assetProfile) { |
|||
register(definition.getJsonId(), assetProfile.getId()); |
|||
createdEntities.put(assetProfile.getUuidId(), new CreatedEntityInfo(assetProfile.getName(), EntityType.ASSET_PROFILE, "Tenant")); |
|||
} |
|||
|
|||
public void register(AssetDefinition definition, Asset asset) { |
|||
register(definition.getJsonId(), asset.getId()); |
|||
createdEntities.put(asset.getUuidId(), new CreatedEntityInfo(asset.getName(), EntityType.ASSET, |
|||
StringUtils.isEmpty(definition.getCustomer()) ? "Tenant" : definition.getCustomer())); |
|||
} |
|||
|
|||
public void register(DeviceDefinition definition, Device device) { |
|||
register(definition.getJsonId(), device.getId()); |
|||
createdEntities.put(device.getUuidId(), new CreatedEntityInfo(device.getName(), EntityType.DEVICE, |
|||
StringUtils.isEmpty(definition.getCustomer()) ? "Tenant" : definition.getCustomer())); |
|||
} |
|||
|
|||
public void register(CustomerDefinition definition, Customer customer) { |
|||
register(definition.getJsonId(), customer.getId()); |
|||
createdEntities.put(customer.getUuidId(), new CreatedEntityInfo(customer.getName(), EntityType.CUSTOMER, "Tenant")); |
|||
} |
|||
|
|||
public void register(CustomerDefinition customerDef, User user) { |
|||
register((String) null, user.getId()); |
|||
createdEntities.put(user.getUuidId(), new CreatedEntityInfo(user.getName(), EntityType.USER, |
|||
StringUtils.isEmpty(customerDef.getName()) ? "Tenant" : customerDef.getName())); |
|||
} |
|||
|
|||
public void register(EdgeDefinition definition, Edge edge) { |
|||
register(definition.getJsonId(), edge.getId()); |
|||
createdEntities.put(edge.getUuidId(), new CreatedEntityInfo(edge.getName(), EntityType.EDGE, |
|||
StringUtils.isEmpty(definition.getCustomer()) ? "Tenant" : definition.getCustomer())); |
|||
} |
|||
|
|||
public void register(DashboardDefinition definition, Dashboard dashboard) { |
|||
register(definition.getJsonId(), dashboard.getId()); |
|||
createdEntities.put(dashboard.getUuidId(), new CreatedEntityInfo(dashboard.getTitle(), EntityType.DASHBOARD, |
|||
StringUtils.isEmpty(definition.getCustomer()) ? "Tenant" : definition.getCustomer())); |
|||
} |
|||
|
|||
public void register(CalculatedField calculatedField) { |
|||
register(calculatedField.getId()); |
|||
EntityId entityId = calculatedField.getEntityId(); |
|||
CreatedEntityInfo entityInfo = createdEntities.get(entityId.getId()); |
|||
boolean alarmRule = false; // TODO: CE doesn't have ALARM calculated field type yet
|
|||
if (entityInfo == null) { |
|||
String target = alarmRule ? "Alarm rule" : "Calculated field"; |
|||
throw new IllegalStateException("Failed to register " + target + " with name: " + |
|||
calculatedField.getName() + " for non-existing entity with id: " + entityId); |
|||
} |
|||
if (alarmRule) { |
|||
createdAlarmRules.put(calculatedField.getUuidId(), new CreatedAlarmRuleInfo(entityId, entityInfo.getName(), calculatedField.getName(), null)); |
|||
return; |
|||
} |
|||
createdCalculatedFields.put(calculatedField.getUuidId(), new CreatedCalculatedFieldInfo(entityId, entityInfo.getName(), calculatedField.getType().name(), calculatedField.getName())); |
|||
} |
|||
|
|||
public void putIdToMap(EntityDefinition entityDefinition, EntityId entityId) { |
|||
putIdToMap(entityDefinition.getEntityType(), entityDefinition.getName(), entityId); |
|||
} |
|||
|
|||
public void putIdToMap(EntityType entityType, String entityName, EntityId entityId) { |
|||
putIdToMap(tenantId, entityType, entityName, entityId); |
|||
} |
|||
|
|||
public void putIdToMap(EntityId ownerId, EntityType entityType, String entityName, EntityId entityId) { |
|||
put(new EntitySearchKey(ownerId, entityType, entityName), entityId); |
|||
} |
|||
|
|||
public void put(EntitySearchKey entitySearchKey, EntityId entityId) { |
|||
entityIdMap.put(entitySearchKey, entityId); |
|||
} |
|||
|
|||
@SuppressWarnings("unchecked") |
|||
public <T extends EntityId> T getIdFromMap(EntityType entityType, String entityName) { |
|||
return (T) entityIdMap.get(new EntitySearchKey(tenantId, entityType, entityName)); |
|||
} |
|||
|
|||
public void put(EntityId entityId, List<RelationDefinition> relations) { |
|||
relationDefinitions.put(entityId, relations); |
|||
} |
|||
|
|||
public void addDeviceCredentials(DeviceCredentialsInfo deviceCredentialsInfo) { |
|||
createdDevices.put(deviceCredentialsInfo.getName(), deviceCredentialsInfo); |
|||
} |
|||
|
|||
public void addUserCredentials(UserCredentialsInfo userCredentialsInfo) { |
|||
createdUsers.put(userCredentialsInfo.getName(), userCredentialsInfo); |
|||
} |
|||
|
|||
public void addEdgeLinkInfo(String edgeName, EdgeLinkInfo edgeLinkInfo) { |
|||
createdEdges.put(edgeName, edgeLinkInfo); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class UserCredentialsInfo { |
|||
|
|||
String name; |
|||
String login; |
|||
String password; |
|||
String customerName; |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class AssetDefinition extends CustomerEntityDefinition { |
|||
|
|||
private String type; |
|||
private String label; |
|||
private String emulator; |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.ASSET; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.asset.AssetProfile; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class AssetProfileDefinition extends AssetProfile { |
|||
|
|||
private String jsonId; |
|||
|
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public abstract class BaseEntityDefinition implements EntityDefinition { |
|||
|
|||
@JsonProperty("name") |
|||
private String name; |
|||
@JsonProperty("attributes") |
|||
private JsonNode attributes; |
|||
@JsonProperty("sharedAttributes") |
|||
private JsonNode sharedAttributes; |
|||
@JsonProperty("relations") |
|||
private List<RelationDefinition> relations = Collections.emptyList(); |
|||
@JsonProperty("jsonId") |
|||
private String jsonId; |
|||
|
|||
public void setRelations(List<RelationDefinition> relations) { |
|||
if (relations != null) { |
|||
this.relations = relations; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.cf.CalculatedField; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class CalculatedFieldDefinition extends CalculatedField { |
|||
|
|||
private Integer reprocessingOrder; |
|||
|
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.service.solutions.data.names.RandomNameData; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@NoArgsConstructor |
|||
public class CustomerDefinition extends BaseEntityDefinition { |
|||
|
|||
private String email; |
|||
private String country; |
|||
private String city; |
|||
private String state; |
|||
private String zip; |
|||
private String address; |
|||
private List<UserDefinition> users = Collections.emptyList(); |
|||
|
|||
@JsonIgnore |
|||
private RandomNameData randomNameData; |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.CUSTOMER; |
|||
} |
|||
|
|||
public void setUsers(List<UserDefinition> users) { |
|||
if (users != null) { |
|||
this.users = users; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public abstract class CustomerEntityDefinition extends BaseEntityDefinition { |
|||
|
|||
private String customer; |
|||
private boolean makePublic; |
|||
|
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class DashboardDefinition extends CustomerEntityDefinition { |
|||
|
|||
private String file; |
|||
private boolean main; |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.DASHBOARD; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class DashboardUserDetailsDefinition { |
|||
|
|||
private String name; |
|||
private boolean fullScreen; |
|||
|
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class DeviceDefinition extends CustomerEntityDefinition { |
|||
|
|||
private String type; |
|||
private String label; |
|||
private String emulator; |
|||
private JsonNode additionalInfo; |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.DEVICE; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.DeviceProfile; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class DeviceProfileDefinition extends DeviceProfile { |
|||
|
|||
private String jsonId; |
|||
|
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class EdgeDefinition extends CustomerEntityDefinition { |
|||
|
|||
private String type; |
|||
private String label; |
|||
private String rootRuleChainId; |
|||
private List<String> ruleChainIds = Collections.emptyList(); |
|||
private List<String> deviceIds = Collections.emptyList(); |
|||
private List<String> assetIds = Collections.emptyList(); |
|||
private List<String> dashboardIds = Collections.emptyList(); |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.EDGE; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.function.Function; |
|||
import java.util.stream.Collectors; |
|||
|
|||
@Data |
|||
public class EmulatorDefinition { |
|||
private String name; |
|||
private String extendz; |
|||
private String clazz; |
|||
private int publishPeriodInDays; |
|||
private int publishFrequencyInSeconds; |
|||
private int publishPauseInMillis; |
|||
private long activityPeriodInMillis; |
|||
private List<TelemetryProfile> telemetryProfiles = Collections.emptyList(); |
|||
|
|||
public void enrich(EmulatorDefinition parent) { |
|||
if (StringUtils.isEmpty(clazz)) { |
|||
clazz = parent.getClazz(); |
|||
} |
|||
if (publishPeriodInDays == 0) { |
|||
publishPeriodInDays = parent.getPublishPeriodInDays(); |
|||
} |
|||
if (publishFrequencyInSeconds == 0) { |
|||
publishFrequencyInSeconds = parent.getPublishFrequencyInSeconds(); |
|||
} |
|||
if (publishPauseInMillis == 0) { |
|||
publishPauseInMillis = parent.getPublishPauseInMillis(); |
|||
} |
|||
if (activityPeriodInMillis == 0L) { |
|||
activityPeriodInMillis = parent.getActivityPeriodInMillis(); |
|||
} |
|||
var profilesMap = telemetryProfiles.stream().collect(Collectors.toMap(TelemetryProfile::getKey, Function.identity())); |
|||
parent.getTelemetryProfiles().forEach(tp -> profilesMap.putIfAbsent(tp.getKey(), tp)); |
|||
telemetryProfiles = new ArrayList<>(profilesMap.values()); |
|||
} |
|||
|
|||
public long getOldestTs(long startTs) { |
|||
return startTs - TimeUnit.DAYS.toMillis(publishPeriodInDays) - publishFrequencyInSeconds; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
public interface EntityDefinition { |
|||
|
|||
String getName(); |
|||
|
|||
EntityType getEntityType(); |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
public class EntitySearchKey { |
|||
|
|||
private final EntityId ownerId; |
|||
private final EntityType entityType; |
|||
private final String entityName; |
|||
|
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class ReferenceableEntityDefinition { |
|||
|
|||
private String name; |
|||
private String file; |
|||
private String update; |
|||
private String jsonId; |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class RelationDefinition { |
|||
|
|||
private EntityType entityType; |
|||
private String entityName; |
|||
private EntitySearchDirection direction; |
|||
private String type; |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.service.solutions.data.values.ValueStrategyDefinition; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
public class TelemetryProfile { |
|||
|
|||
private String key; |
|||
private ValueStrategyDefinition valueStrategy; |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
@Data |
|||
public class TenantDefinition extends BaseEntityDefinition { |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.TENANT; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.definition; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
public class UserDefinition extends BaseEntityDefinition { |
|||
|
|||
private String firstname; |
|||
private String lastname; |
|||
private String password; |
|||
private DashboardUserDetailsDefinition dashboard; |
|||
|
|||
@Override |
|||
public EntityType getEntityType() { |
|||
return EntityType.USER; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,195 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.emulator; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import lombok.Data; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.data.util.Pair; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.cluster.TbClusterService; |
|||
import org.thingsboard.server.common.data.HasName; |
|||
import org.thingsboard.server.common.data.HasTenantId; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
import org.thingsboard.server.common.data.id.HasId; |
|||
import org.thingsboard.server.common.data.msg.TbMsgType; |
|||
import org.thingsboard.server.common.msg.TbMsg; |
|||
import org.thingsboard.server.common.msg.TbMsgDataType; |
|||
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|||
import org.thingsboard.server.queue.TbQueueCallback; |
|||
import org.thingsboard.server.queue.TbQueueMsgMetadata; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
|||
import org.thingsboard.server.service.solutions.data.definition.EmulatorDefinition; |
|||
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
|||
|
|||
import java.util.concurrent.CompletableFuture; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.ScheduledFuture; |
|||
import java.util.concurrent.TimeUnit; |
|||
import java.util.concurrent.atomic.AtomicInteger; |
|||
|
|||
@Slf4j |
|||
@Data |
|||
public abstract class AbstractEmulatorLauncher<T extends HasId<? extends EntityId> & HasTenantId & HasName> { |
|||
|
|||
protected static final TbQueueCallback EMPTY_CALLBACK = new TbQueueCallback() { |
|||
@Override |
|||
public void onSuccess(TbQueueMsgMetadata metadata) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
|
|||
} |
|||
}; |
|||
protected final T entity; |
|||
protected final EmulatorDefinition emulatorDefinition; |
|||
protected final ExecutorService oldTelemetryExecutor; |
|||
protected final TbClusterService tbClusterService; |
|||
protected final PartitionService partitionService; |
|||
protected final TbQueueProducerProvider tbQueueProducerProvider; |
|||
protected final TbServiceInfoProvider serviceInfoProvider; |
|||
protected final TelemetrySubscriptionService tsSubService; |
|||
protected final long publishFrequency; |
|||
|
|||
private final Emulator emulator; |
|||
private ScheduledFuture<?> scheduledFuture; |
|||
|
|||
public AbstractEmulatorLauncher(T entity, |
|||
EmulatorDefinition emulatorDefinition, |
|||
ExecutorService oldTelemetryExecutor, |
|||
TbClusterService tbClusterService, |
|||
PartitionService partitionService, |
|||
TbQueueProducerProvider tbQueueProducerProvider, |
|||
TbServiceInfoProvider serviceInfoProvider, |
|||
TelemetrySubscriptionService tsSubService) throws Exception { |
|||
this.entity = entity; |
|||
this.emulatorDefinition = emulatorDefinition; |
|||
this.oldTelemetryExecutor = oldTelemetryExecutor; |
|||
this.tbClusterService = tbClusterService; |
|||
this.partitionService = partitionService; |
|||
this.tbQueueProducerProvider = tbQueueProducerProvider; |
|||
this.serviceInfoProvider = serviceInfoProvider; |
|||
this.tsSubService = tsSubService; |
|||
this.publishFrequency = TimeUnit.SECONDS.toMillis(Math.max(emulatorDefinition.getPublishFrequencyInSeconds(), 1)); |
|||
if (StringUtils.isEmpty(emulatorDefinition.getClazz())) { |
|||
emulator = new BasicEmulator(); |
|||
} else { |
|||
emulator = (Emulator) Class.forName(emulatorDefinition.getClazz()).getDeclaredConstructor().newInstance(); |
|||
} |
|||
emulator.init(emulatorDefinition); |
|||
} |
|||
|
|||
public CompletableFuture<Void> launch() { |
|||
final long oldestTs = emulatorDefinition.getOldestTs(System.currentTimeMillis()); |
|||
CompletableFuture<Void> future = new CompletableFuture<>(); |
|||
oldTelemetryExecutor.submit(() -> { |
|||
AtomicInteger pending = new AtomicInteger(1); |
|||
try { |
|||
if (emulator instanceof SimpleEmulator) { |
|||
if (oldestTs < (System.currentTimeMillis() - publishFrequency)) { |
|||
pushOldTelemetry(oldestTs, pending, future); |
|||
} |
|||
} else if (emulator instanceof CustomEmulator customEmulator) { |
|||
Pair<Long, ObjectNode> telemetry = customEmulator.getNextValue(); |
|||
while (telemetry != null) { |
|||
pending.incrementAndGet(); |
|||
publishTelemetry(telemetry.getFirst(), telemetry.getSecond(), pending, future); |
|||
telemetry = customEmulator.getNextValue(); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
log.warn("Telemetry upload failed for {}", entity.getName(), e); |
|||
future.completeExceptionally(e); |
|||
} finally { |
|||
if (pending.decrementAndGet() == 0 && !future.isDone()) { |
|||
log.trace("[{}] Telemetry emulation finished. Producer and all callbacks completed successfully", entity.getName()); |
|||
future.complete(null); |
|||
} |
|||
} |
|||
|
|||
future.whenComplete((v, t) -> { |
|||
try { |
|||
postProcessEntity(entity); |
|||
} catch (Exception e) { |
|||
log.warn("Post-processing failed for {}", entity.getName(), e); |
|||
} |
|||
}); |
|||
|
|||
}); |
|||
return future; |
|||
} |
|||
|
|||
protected void postProcessEntity(T entity) { |
|||
} |
|||
|
|||
private void pushOldTelemetry(long oldestTs, AtomicInteger pending, CompletableFuture<Void> future) throws InterruptedException { |
|||
for (long ts = oldestTs; ts < System.currentTimeMillis(); ts += publishFrequency) { |
|||
pending.incrementAndGet(); |
|||
publishTelemetry(ts, pending, future); |
|||
} |
|||
} |
|||
|
|||
private void publishTelemetry(long ts, AtomicInteger pending, CompletableFuture<Void> future) throws InterruptedException { |
|||
ObjectNode values = ((SimpleEmulator) emulator).getValue(ts); |
|||
publishTelemetry(ts, values, pending, future); |
|||
Thread.sleep(emulatorDefinition.getPublishPauseInMillis()); |
|||
} |
|||
|
|||
public void stop() { |
|||
scheduledFuture.cancel(true); |
|||
} |
|||
|
|||
private void publishTelemetry(long ts, ObjectNode value, AtomicInteger pending, CompletableFuture<Void> future) { |
|||
String msgData = JacksonUtil.toString(value); |
|||
log.debug("[{}] Publishing telemetry: {}", entity.getName(), msgData); |
|||
TbMsgMetaData md = new TbMsgMetaData(); |
|||
md.putValue("ts", Long.toString(ts)); |
|||
TbMsg tbMsg = TbMsg.newMsg() |
|||
.type(TbMsgType.POST_TELEMETRY_REQUEST) |
|||
.originator(entity.getId()) |
|||
.copyMetaData(md) |
|||
.dataType(TbMsgDataType.JSON) |
|||
.data(msgData) |
|||
.build(); |
|||
|
|||
TbQueueCallback callback = new TbQueueCallback() { |
|||
|
|||
@Override |
|||
public void onSuccess(TbQueueMsgMetadata metadata) { |
|||
log.debug("[{}] Successfully pushed message to Rule Engine for ts {}", entity.getName(), ts); |
|||
if (pending.decrementAndGet() == 0 && !future.isDone()) { |
|||
log.trace("[{}] Completing emulation future from callback for msg with ts: {}", entity.getName(), ts); |
|||
future.complete(null); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable t) { |
|||
log.warn("[{}] Telemetry upload failed for ts {}", entity.getName(), ts, t); |
|||
if (pending.decrementAndGet() == 0 && !future.isDone()) { |
|||
future.completeExceptionally(t); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
tbClusterService.pushMsgToRuleEngine(entity.getTenantId(), entity.getId(), tbMsg, callback); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.emulator; |
|||
|
|||
import lombok.Builder; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.thingsboard.server.cluster.TbClusterService; |
|||
import org.thingsboard.server.common.data.asset.Asset; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
|||
import org.thingsboard.server.service.solutions.data.definition.EmulatorDefinition; |
|||
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
|||
|
|||
import java.util.concurrent.ExecutorService; |
|||
|
|||
@Slf4j |
|||
public class AssetEmulatorLauncher extends AbstractEmulatorLauncher<Asset> { |
|||
|
|||
@Builder |
|||
public AssetEmulatorLauncher(Asset entity, EmulatorDefinition emulatorDefinition, ExecutorService oldTelemetryExecutor, TbClusterService tbClusterService, |
|||
PartitionService partitionService, |
|||
TbQueueProducerProvider tbQueueProducerProvider, |
|||
TbServiceInfoProvider serviceInfoProvider, |
|||
TelemetrySubscriptionService tsSubService) throws Exception { |
|||
super(entity, emulatorDefinition, oldTelemetryExecutor, tbClusterService, partitionService, tbQueueProducerProvider, serviceInfoProvider, tsSubService); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.emulator; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.service.solutions.data.definition.EmulatorDefinition; |
|||
import org.thingsboard.server.service.solutions.data.values.TelemetryGenerator; |
|||
import org.thingsboard.server.service.solutions.data.values.TelemetryGeneratorFactory; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
public class BasicEmulator implements SimpleEmulator { |
|||
|
|||
private final Map<String, TelemetryGenerator> tsGenerators = new HashMap<>(); |
|||
|
|||
@Override |
|||
public void init(EmulatorDefinition emulatorDefinition) { |
|||
emulatorDefinition.getTelemetryProfiles().forEach(tp -> tsGenerators.put(tp.getKey(), TelemetryGeneratorFactory.create(tp))); |
|||
} |
|||
|
|||
@Override |
|||
public ObjectNode getValue(long ts) { |
|||
ObjectNode values = JacksonUtil.newObjectNode(); |
|||
tsGenerators.values().forEach(gen -> gen.addValue(ts, values)); |
|||
return values; |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.emulator; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.springframework.data.util.Pair; |
|||
|
|||
public interface CustomEmulator extends Emulator { |
|||
|
|||
Pair<Long, ObjectNode> getNextValue(); |
|||
|
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.emulator; |
|||
|
|||
import com.google.common.util.concurrent.FutureCallback; |
|||
import lombok.Builder; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.checkerframework.checker.nullness.qual.Nullable; |
|||
import org.thingsboard.rule.engine.api.AttributesSaveRequest; |
|||
import org.thingsboard.server.cluster.TbClusterService; |
|||
import org.thingsboard.server.common.data.AttributeScope; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.kv.LongDataEntry; |
|||
import org.thingsboard.server.common.msg.queue.ServiceType; |
|||
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; |
|||
import org.thingsboard.server.gen.transport.TransportProtos; |
|||
import org.thingsboard.server.queue.common.TbProtoQueueMsg; |
|||
import org.thingsboard.server.queue.discovery.PartitionService; |
|||
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
|||
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; |
|||
import org.thingsboard.server.service.solutions.data.definition.EmulatorDefinition; |
|||
import org.thingsboard.server.service.state.DefaultDeviceStateService; |
|||
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
|||
|
|||
import java.util.UUID; |
|||
import java.util.concurrent.ExecutorService; |
|||
|
|||
@Slf4j |
|||
public class DeviceEmulatorLauncher extends AbstractEmulatorLauncher<Device> { |
|||
|
|||
@Builder |
|||
public DeviceEmulatorLauncher(Device entity, |
|||
EmulatorDefinition emulatorDefinition, |
|||
ExecutorService oldTelemetryExecutor, |
|||
TbClusterService tbClusterService, |
|||
PartitionService partitionService, |
|||
TbQueueProducerProvider tbQueueProducerProvider, |
|||
TbServiceInfoProvider serviceInfoProvider, |
|||
TelemetrySubscriptionService tsSubService) throws Exception { |
|||
super(entity, emulatorDefinition, oldTelemetryExecutor, tbClusterService, partitionService, tbQueueProducerProvider, serviceInfoProvider, tsSubService); |
|||
} |
|||
|
|||
@Override |
|||
protected void postProcessEntity(Device entity) { |
|||
if (this.emulatorDefinition.getActivityPeriodInMillis() > 0) { |
|||
tsSubService.saveAttributes(AttributesSaveRequest.builder() |
|||
.tenantId(entity.getTenantId()) |
|||
.entityId(entity.getId()) |
|||
.scope(AttributeScope.SERVER_SCOPE) |
|||
.entry(new LongDataEntry(DefaultDeviceStateService.INACTIVITY_TIMEOUT, this.emulatorDefinition.getActivityPeriodInMillis())) |
|||
.callback(new FutureCallback<>() { |
|||
@Override |
|||
public void onSuccess(@Nullable Void unused) { |
|||
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, entity.getTenantId(), entity.getId()); |
|||
UUID sessionId = UUID.randomUUID(); |
|||
TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder() |
|||
.setSessionInfo(TransportProtos.SessionInfoProto.newBuilder() |
|||
.setSessionIdMSB(sessionId.getMostSignificantBits()) |
|||
.setSessionIdLSB(sessionId.getLeastSignificantBits()) |
|||
.setDeviceIdMSB(entity.getId().getId().getMostSignificantBits()) |
|||
.setDeviceIdLSB(entity.getId().getId().getLeastSignificantBits()) |
|||
.setDeviceProfileIdMSB(entity.getId().getId().getMostSignificantBits()) |
|||
.setDeviceProfileIdLSB(entity.getId().getId().getLeastSignificantBits()) |
|||
.setDeviceName(entity.getName()) |
|||
.setDeviceType(entity.getType()) |
|||
.setTenantIdMSB(entity.getTenantId().getId().getMostSignificantBits()) |
|||
.setTenantIdLSB(entity.getTenantId().getId().getLeastSignificantBits()) |
|||
.setNodeId(serviceInfoProvider.getServiceId()) |
|||
.build()) |
|||
.setSubscriptionInfo(TransportProtos.SubscriptionInfoProto.newBuilder().setLastActivityTime(System.currentTimeMillis()).build()) |
|||
.build(); |
|||
tbQueueProducerProvider.getTbCoreMsgProducer().send(tpi, |
|||
new TbProtoQueueMsg<>(entity.getUuidId(), |
|||
TransportProtos.ToCoreMsg.newBuilder().setToDeviceActorMsg(msg).build()), EMPTY_CALLBACK |
|||
); |
|||
} |
|||
|
|||
@Override |
|||
public void onFailure(Throwable throwable) { |
|||
} |
|||
}) |
|||
.build()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.emulator; |
|||
|
|||
import org.thingsboard.server.service.solutions.data.definition.EmulatorDefinition; |
|||
|
|||
public interface Emulator { |
|||
|
|||
void init(EmulatorDefinition emulatorDefinition); |
|||
|
|||
} |
|||
@ -0,0 +1,108 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.emulator; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.springframework.data.util.Pair; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
import org.thingsboard.server.service.solutions.data.definition.EmulatorDefinition; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Calendar; |
|||
import java.util.List; |
|||
import java.util.TimeZone; |
|||
|
|||
public class FieldIrrigationStateEmulator implements CustomEmulator { |
|||
|
|||
private static final int START_HOUR = 6; |
|||
private int idx; |
|||
private List<Pair<Long, ObjectNode>> data = new ArrayList<>(); |
|||
|
|||
@Override |
|||
public void init(EmulatorDefinition emulatorDefinition) { |
|||
idx = 0; |
|||
var tz = TimeZone.getTimeZone("America/New_York"); |
|||
Calendar c = Calendar.getInstance(); |
|||
c.setTimeInMillis(System.currentTimeMillis()); |
|||
c.setTimeZone(tz); |
|||
int curHour = c.get(Calendar.HOUR_OF_DAY); |
|||
c.add(Calendar.DAY_OF_MONTH, curHour < START_HOUR ? -7 : -6); |
|||
c.set(Calendar.HOUR_OF_DAY, START_HOUR); |
|||
c.set(Calendar.MINUTE, 0); |
|||
|
|||
add(c, JacksonUtil.newObjectNode() |
|||
.put("startTs", c.getTimeInMillis()) |
|||
.put("durationThreshold", 30 * 60000) |
|||
.put("consumption", 423) |
|||
.put("duration", 30 * 60000) |
|||
); |
|||
add(c, JacksonUtil.newObjectNode() |
|||
.put("startTs", c.getTimeInMillis()) |
|||
.put("durationThreshold", 30 * 60000) |
|||
.put("consumption", 407) |
|||
.put("duration", 30 * 60000) |
|||
); |
|||
add(c, JacksonUtil.newObjectNode() |
|||
.put("startTs", c.getTimeInMillis()) |
|||
.put("consumptionThreshold", 1000) |
|||
.put("consumption", 1000) |
|||
.put("duration", 63 * 60000) |
|||
); |
|||
add(c, JacksonUtil.newObjectNode() |
|||
.put("startTs", c.getTimeInMillis()) |
|||
.put("consumptionThreshold", 500) |
|||
.put("consumption", 500) |
|||
.put("duration", 36 * 60000) |
|||
); |
|||
add(c, JacksonUtil.newObjectNode() |
|||
.put("startTs", c.getTimeInMillis()) |
|||
.put("consumptionThreshold", 1000) |
|||
.put("consumption", 1000) |
|||
.put("duration", 63 * 60000) |
|||
); |
|||
add(c, JacksonUtil.newObjectNode() |
|||
.put("startTs", c.getTimeInMillis()) |
|||
.put("durationThreshold", 30 * 60000) |
|||
.put("duration", 30 * 60000) |
|||
.put("consumption", 452) |
|||
); |
|||
add(c, JacksonUtil.newObjectNode() |
|||
.put("startTs", c.getTimeInMillis()) |
|||
.put("durationThreshold", 30 * 60000) |
|||
.put("duration", 30 * 60000) |
|||
.put("consumption", 447) |
|||
); |
|||
} |
|||
|
|||
private void add(Calendar c, ObjectNode objectNode) { |
|||
ObjectNode startIrrigationMsg = JacksonUtil.newObjectNode(); |
|||
startIrrigationMsg.put("irrigationState", "DONE"); |
|||
startIrrigationMsg.set("irrigationTask", objectNode); |
|||
data.add(Pair.of(c.getTimeInMillis(), startIrrigationMsg)); |
|||
c.add(Calendar.DAY_OF_MONTH, 1); |
|||
} |
|||
|
|||
@Override |
|||
public Pair<Long, ObjectNode> getNextValue() { |
|||
if (idx < data.size()) { |
|||
var result = data.get(idx); |
|||
idx++; |
|||
return result; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.emulator; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
|
|||
public interface SimpleEmulator extends Emulator { |
|||
|
|||
ObjectNode getValue(long ts); |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.names; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class RandomNameData { |
|||
|
|||
private final String firstName; |
|||
private final String lastName; |
|||
private final String email; |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.names; |
|||
|
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
|
|||
import java.util.Random; |
|||
|
|||
public class RandomNameUtil { |
|||
|
|||
private static final Random RANDOM = new Random(System.currentTimeMillis()); |
|||
|
|||
private static String[] FIRST_NAMES = new String[]{ |
|||
"Nadia", "Lynne", "Lera", "Holly", "Zina", "Mandi", "Kasie", "Josef", "Liane", "Man", |
|||
"Karan", "Nga", "Tesha", "Elva", "Bree", "Ida", "Mimi", "Altha", "Omar", "Ollie", |
|||
"Nicol", "Ned", "Teri", "Sabra", "Kari", "Eva", "Penni", "Simon", "Kaci", "Jess", |
|||
"Bea", "Ron", "Melba", "Vella", "Nada", "Cyndi", "Audry", "Helga", "Chana", "Lola", |
|||
"Terry", "Tami", "Dedra", "Erwin", "Anne", "Zoila", "Nelia", "Hyman", "Deane", "Erica", |
|||
"Amos", "Maura", "Gwenn", "Evan", "Lelia", "Grant", "Abe", "Fanny", "Cindi", "Pilar", |
|||
"Darcy", "Len", "Casie", "Jose", "Kylie", "Cami", "Casey", "Kerri", "Bruno", "Theda", |
|||
"Ardis", "Carin", "Belia", "Jeff", "Yetta", "Ola", "Lyla", "Megan", "Zita", "Rocky", |
|||
"Darci", "Dale", "Mirta", "Tanja", "Stacy", "Julie", "Aisha", "Avis", "Hugh", "Anh", |
|||
"Bud", "Earle", "Ossie", "Odell", "Dovie", "Afton", "Joel", "Aida", "Vina", "Emiko", |
|||
"Tori", "Keena", "Addie", "Nancy", "Tonda", "Josue", "Dina", "Mitch", "Thea", "Cole", |
|||
"Lyman", "Donna", "Roma", "Deena", "Lue", "Bette", "Ilene", "Vera", "Kirby", "Lenna", |
|||
"Pat", "Grace", "Gus", "Frank", "Lena", "Adele", "Kerry", "Santo", "Wade", "Trudi", |
|||
"Bruce", "Raul", "Katy", "Heidi", "Marna", "Faith", "Ronna", "Kylee", "Emma", "Luna", |
|||
"Ilda", "Mose", "Juan", "Cori", "Gayla", "Otha", "Jung", "Bambi", "Joi", "Sibyl", |
|||
"Reid", "Bonny", "Roni", "Joy", "Farah", "Jeane", "Jill", "Tisha", "Leon", "Leigh", |
|||
"Inge", "Zella", "Rubin", "Carri", "Nana", "Sofia", "Lucio", "Eboni", "Adam", "Lino", |
|||
"Elvis", "Marci", "Adina", "Nanci", "Joyce", "James", "Louis", "Aurea", "Chi", "Kathy", |
|||
"Nina", "Lise", "Reda", "Candi", "Ralph", "Velva", "Mari", "Mavis", "Kory", "Tammi", |
|||
"Lucy", "Lavon", "Ana", "Lin", "Alena", "Haley", "Myra", "Amy", "Marlo", "Ami" |
|||
}; |
|||
|
|||
private static String[] LAST_NAMES = new String[]{ |
|||
"Vinson", "Rogers", "Burkett", "Gamboa", "Gross", "Toledo", "Kiser", "Harman", "Pierce", "Lovett", |
|||
"Sexton", "Coates", "Seymour", "Holland", "Finley", "Hagen", "Schmitt", "Beach", "Rea", "Peck", |
|||
"Noel", "Archer", "Zamora", "Wood", "Rivera", "Lentz", "Alonso", "Rider", "Story", "Schwab", |
|||
"Mercado", "Caruso", "Bynum", "Spears", "Bingham", "Tyler", "Ponce", "Skaggs", "Matos", "Stinson", |
|||
"Burgos", "Coles", "Helton", "Joyce", "Galvan", "Grant", "Stahl", "Daigle", "Hayden", "Quiroz", |
|||
"Horne", "Shaw", "Addison", "Harvey", "Watkins", "Romano", "Ahmed", "Lind", "Swenson", "Kemp", |
|||
"Barnes", "Horton", "Dugan", "Farr", "Swanson", "Hale", "Hidalgo", "Schulte", "Ervin", "Osorio", |
|||
"Lincoln", "Zhang", "Oakes", "Stiles", "Lu", "Culver", "Singer", "Knott", "Bullard", "Butts", |
|||
"Coley", "Roth", "Cobb", "Darnell", "Vaughan", "Conrad", "Austin", "Rico", "Hobson", "Brunner", |
|||
"Mccray", "Mattson", "Barber", "Clement", "Ly", "Dejesus", "Painter", "Macias", "Healy", "Duncan", |
|||
"Forrest", "Hurley", "Torres", "Blue", "Rucker", "Ferrell", "Maddox", "Osborne", "Holt", "Briggs", |
|||
"Irvin", "Bassett", "Urban", "Overton", "Sloan", "Guzman", "Estrada", "Redding", "Gold", "Paz", |
|||
"Lucero", "Reyes", "Conner", "Keith", "Hays", "Hutton", "Moore", "Tuttle", "Powers", "William", |
|||
"Reeder", "Castro", "Vela", "Baca", "Frazier", "Kline", "Huggins", "Zepeda", "Pate", "Marion", |
|||
"Hollis", "Ybarra", "Cuellar", "Gallo", "Gomes", "Louis", "Bolden", "Liu", "Hayes", "Simpson", |
|||
"Otero", "Conway", "Peoples", "Mohr", "Loomis", "Engel", "Hughes", "Chapman", "Bower", "Schmidt", |
|||
"Mcmahon", "Booth", "Ward", "Drake", "Cutler", "Gordon", "Black", "Clifton", "Curran", "Pierre", |
|||
"Workman", "Tilley", "Moody", "Kuhn", "Eastman", "Scott", "Vang", "Tillman", "Cleary", "Yi", |
|||
"Griffin", "Mendoza", "Mason", "Pittman", "Landis", "Gay", "Rowland", "Jordan", "Lo", "Kumar", |
|||
"Vernon", "Boyd", "Rocha", "Mcqueen", "Cornett", "Deaton", "Borden", "Lacey", "Gaston", "Miles" |
|||
}; |
|||
|
|||
public static String nextFirstName() { |
|||
return randomElement(FIRST_NAMES); |
|||
} |
|||
|
|||
public static String nextLastName() { |
|||
return randomElement(LAST_NAMES); |
|||
} |
|||
|
|||
private static String toEmail(String firstName, String lastName) { |
|||
return firstName.toLowerCase() + "." + lastName.toLowerCase() + "@thingsboard.io"; |
|||
} |
|||
|
|||
public static RandomNameData next() { |
|||
var firstName = nextFirstName(); |
|||
var lastName = nextLastName(); |
|||
return new RandomNameData(firstName, lastName, toEmail(firstName, lastName)); |
|||
} |
|||
|
|||
public static RandomNameData nextSuperRandom() { |
|||
var firstName = nextFirstName() + StringUtils.randomAlphanumeric(10).toLowerCase(); |
|||
var lastName = nextLastName() + StringUtils.randomAlphanumeric(10).toLowerCase(); |
|||
return new RandomNameData(firstName, lastName, toEmail(firstName, lastName)); |
|||
} |
|||
|
|||
private static String randomElement(String[] array) { |
|||
return array[RANDOM.nextInt(array.length)]; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.solution; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
@Schema |
|||
@Data |
|||
public class SolutionInstallResponse extends TenantSolutionTemplateInstructions { |
|||
|
|||
@Schema(description = "Indicates that template was installed successfully") |
|||
private boolean success; |
|||
@Schema(description = "List of entity IDs created during solution installation") |
|||
private List<EntityId> createdEntityIds; |
|||
@Schema(description = "What keys to delete during template uninstall") |
|||
private List<String> tenantTelemetryKeys; |
|||
@Schema(description = "What attributes to delete during template uninstall") |
|||
private List<String> tenantAttributeKeys; |
|||
|
|||
public SolutionInstallResponse(TenantSolutionTemplateInstructions instructions, boolean success, List<EntityId> createdEntityIds) { |
|||
this(instructions, success, createdEntityIds, Collections.emptyList(), Collections.emptyList()); |
|||
} |
|||
|
|||
public SolutionInstallResponse(TenantSolutionTemplateInstructions instructions, boolean success, List<EntityId> createdEntityIds, |
|||
List<String> tenantTelemetryKeys, List<String> tenantAttributeKeys) { |
|||
super(instructions); |
|||
this.success = success; |
|||
this.createdEntityIds = createdEntityIds; |
|||
this.tenantTelemetryKeys = tenantTelemetryKeys; |
|||
this.tenantAttributeKeys = tenantAttributeKeys; |
|||
} |
|||
|
|||
public SolutionInstallResponse() { |
|||
super(); |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.solution; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
|
|||
@Schema |
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class TenantSolutionTemplateInstructions { |
|||
|
|||
@Schema(description = "Id of the main dashboard of the solution") |
|||
private DashboardId dashboardId; |
|||
@Schema(description = "Id of the public customer if solution has public entities") |
|||
private CustomerId publicId; |
|||
@Schema(description = "Is the main dashboard public") |
|||
private boolean mainDashboardPublic; |
|||
@Schema(description = "Markdown with solution usage instructions") |
|||
private String details; |
|||
|
|||
public TenantSolutionTemplateInstructions(TenantSolutionTemplateInstructions instructions) { |
|||
this.dashboardId = instructions.getDashboardId(); |
|||
this.publicId = instructions.getPublicId(); |
|||
this.mainDashboardPublic = instructions.isMainDashboardPublic(); |
|||
this.details = instructions.getDetails(); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class CompositeValueStrategyDefinition implements ValueStrategyDefinition { |
|||
|
|||
private ValueStrategyDefinition defaultHours; |
|||
private ValueStrategyDefinition workHours; |
|||
private ValueStrategyDefinition nightHours; |
|||
private ValueStrategyDefinition holidayHours; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.COMPOSITE; |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
public class CompositeValueStrategyGenerator extends TelemetryGenerator { |
|||
|
|||
TelemetryGenerator defaultGenerator; |
|||
TelemetryGenerator whGenerator; |
|||
TelemetryGenerator nhGenerator; |
|||
TelemetryGenerator hhGenerator; |
|||
|
|||
public CompositeValueStrategyGenerator(TelemetryProfile tp) { |
|||
super(tp); |
|||
var def = (CompositeValueStrategyDefinition) tp.getValueStrategy(); |
|||
defaultGenerator = TelemetryGeneratorFactory.create(new TelemetryProfile(tp.getKey(), def.getDefaultHours())); |
|||
if (def.getWorkHours() != null) { |
|||
whGenerator = TelemetryGeneratorFactory.create(new TelemetryProfile(tp.getKey(), def.getWorkHours())); |
|||
} |
|||
if (def.getNightHours() != null) { |
|||
nhGenerator = TelemetryGeneratorFactory.create(new TelemetryProfile(tp.getKey(), def.getNightHours())); |
|||
} |
|||
if (def.getHolidayHours() != null) { |
|||
hhGenerator = TelemetryGeneratorFactory.create(new TelemetryProfile(tp.getKey(), def.getHolidayHours())); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
if (hhGenerator != null && GeneratorTools.isHoliday(ts)) { |
|||
hhGenerator.addValue(ts, values); |
|||
} else if (nhGenerator != null && GeneratorTools.isNightHour(ts)) { |
|||
nhGenerator.addValue(ts, values); |
|||
} else if (whGenerator != null && GeneratorTools.isWorkHour(ts)) { |
|||
whGenerator.addValue(ts, values); |
|||
} else { |
|||
defaultGenerator.addValue(ts, values); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
public class ConstantTelemetryGenerator extends TelemetryGenerator { |
|||
|
|||
private final ConstantValueStrategyDefinition strategy; |
|||
private JsonNode value; |
|||
|
|||
public ConstantTelemetryGenerator(TelemetryProfile telemetryProfile) { |
|||
super(telemetryProfile); |
|||
this.strategy = (ConstantValueStrategyDefinition) telemetryProfile.getValueStrategy(); |
|||
this.value = strategy.getValue(); |
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
values.set(key, value); |
|||
} |
|||
|
|||
@Override |
|||
public double getValue() { |
|||
if (value != null && value.isNumber()) { |
|||
return value.doubleValue(); |
|||
} else { |
|||
return super.getValue(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void setValue(double value) { |
|||
// do nothing;
|
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class ConstantValueStrategyDefinition implements ValueStrategyDefinition { |
|||
private JsonNode value; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.CONSTANT; |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.math.RoundingMode; |
|||
|
|||
import static org.thingsboard.server.service.solutions.data.values.GeneratorTools.getMultiplier; |
|||
import static org.thingsboard.server.service.solutions.data.values.GeneratorTools.randomDouble; |
|||
|
|||
public class CounterTelemetryGenerator extends TelemetryGenerator { |
|||
|
|||
private final CounterValueStrategyDefinition strategy; |
|||
@Getter @Setter |
|||
private double value; |
|||
|
|||
public CounterTelemetryGenerator(TelemetryProfile telemetryProfile) { |
|||
super(telemetryProfile); |
|||
this.strategy = (CounterValueStrategyDefinition) telemetryProfile.getValueStrategy(); |
|||
this.value = getRandomStartValue(); |
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
double step = randomDouble(strategy.getMinIncrement(), strategy.getMaxIncrement()); |
|||
double multiplier = getMultiplier(ts, strategy.getHolidayMultiplier(), strategy.getWorkHoursMultiplier(), strategy.getNightHoursMultiplier()); |
|||
value += step * multiplier; |
|||
if (value > getRandomEndValue()) { |
|||
value = getRandomStartValue(); |
|||
} |
|||
put(values, value); |
|||
} |
|||
|
|||
private void put(ObjectNode values, double value) { |
|||
if (strategy.getPrecision() == 0) { |
|||
values.put(key, (int) value); |
|||
} else { |
|||
values.put(key, BigDecimal.valueOf(value) |
|||
.setScale(strategy.getPrecision(), RoundingMode.HALF_UP) |
|||
.doubleValue()); |
|||
} |
|||
} |
|||
|
|||
public double getRandomStartValue() { |
|||
return randomDouble(strategy.getMinStartValue(), strategy.getMaxStartValue()); |
|||
} |
|||
|
|||
public double getRandomEndValue() { |
|||
return randomDouble(strategy.getMinEndValue(), strategy.getMaxEndValue()); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class CounterValueStrategyDefinition implements ValueStrategyDefinition { |
|||
|
|||
private int precision; |
|||
private double minStartValue; |
|||
private double maxStartValue; |
|||
private double minEndValue; |
|||
private double maxEndValue; |
|||
private double minIncrement; |
|||
private double maxIncrement; |
|||
private double holidayMultiplier; |
|||
private double workHoursMultiplier; |
|||
private double nightHoursMultiplier; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.COUNTER; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
import static org.thingsboard.server.service.solutions.data.values.GeneratorTools.randomDouble; |
|||
|
|||
public class DecrementTelemetryGenerator extends IncDecTelemetryGenerator<DecrementValueStrategyDefinition> { |
|||
|
|||
public DecrementTelemetryGenerator(TelemetryProfile telemetryProfile) { |
|||
super(telemetryProfile); |
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
double step = randomDouble(strategy.getMinDecrement(), strategy.getMaxDecrement()); |
|||
double newValue = value - step; |
|||
value = Math.max(newValue, endValue); |
|||
put(values, value); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class DecrementValueStrategyDefinition extends IncDecValueStrategyDefinition { |
|||
|
|||
private double minDecrement; |
|||
private double maxDecrement; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.DECREMENT; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
import static org.thingsboard.server.service.solutions.data.values.GeneratorTools.randomLong; |
|||
|
|||
|
|||
public class EventTelemetryGenerator extends TelemetryGenerator { |
|||
|
|||
private final EventValueStrategyDefinition strategy; |
|||
private final long currentAnomaly; |
|||
private Object value; |
|||
|
|||
public EventTelemetryGenerator(TelemetryProfile telemetryProfile) { |
|||
super(telemetryProfile); |
|||
this.strategy = (EventValueStrategyDefinition) telemetryProfile.getValueStrategy(); |
|||
currentAnomaly = randomLong(0, strategy.getAnomalyChance()); |
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
boolean anomaly = false; |
|||
if (randomLong(0, strategy.getAnomalyChance()) == currentAnomaly) { |
|||
anomaly = true; |
|||
} |
|||
this.value = anomaly ? strategy.getAnomalyValue() : strategy.getNormalValue(); |
|||
if (value instanceof Boolean) { |
|||
values.put(key, (Boolean) value); |
|||
} else if (value instanceof Double) { |
|||
values.put(key, ((Double) value)); |
|||
} else if (value instanceof Integer) { |
|||
values.put(key, ((Integer) value)); |
|||
} else { |
|||
throw new RuntimeException("Not supported value for event telemetry generator: " + value); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class EventValueStrategyDefinition implements ValueStrategyDefinition { |
|||
private Object normalValue; |
|||
private Object anomalyValue; |
|||
private double anomalyChance; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.EVENT; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import java.util.Calendar; |
|||
import java.util.Date; |
|||
import java.util.Random; |
|||
import java.util.TimeZone; |
|||
|
|||
public class GeneratorTools { |
|||
|
|||
private static final Random random = new Random(); |
|||
|
|||
public static long randomLong(double min, double max) { |
|||
return (long) randomDouble(min, max); |
|||
} |
|||
|
|||
public static double randomDouble(double min, double max) { |
|||
return min + random.nextDouble() * Math.abs(max - min); |
|||
} |
|||
|
|||
public static double getMultiplier(long ts, double holidayMultiplier, double workHoursMultiplier, double nightHoursMultiplier) { |
|||
Date date = new Date(ts); |
|||
Calendar c = Calendar.getInstance(); |
|||
c.setTime(date); |
|||
int dayOfWeek = c.get(Calendar.DAY_OF_WEEK); |
|||
int hour = c.get(Calendar.HOUR_OF_DAY); |
|||
double multiplier = 1.0; |
|||
if (dayOfWeek == 1 || dayOfWeek == 7) { |
|||
multiplier *= holidayMultiplier; |
|||
} |
|||
|
|||
if (hour > 8 && hour < 18) { |
|||
multiplier *= workHoursMultiplier; |
|||
} else if (hour < 6 || hour > 22) { |
|||
multiplier *= nightHoursMultiplier; |
|||
} |
|||
return multiplier; |
|||
} |
|||
|
|||
public static int getHour(TimeZone tz, long ts) { |
|||
Date date = new Date(ts); |
|||
Calendar c = Calendar.getInstance(); |
|||
c.setTimeZone(tz); |
|||
c.setTime(date); |
|||
return c.get(Calendar.HOUR_OF_DAY); |
|||
} |
|||
|
|||
public static int getMinute(TimeZone tz, long ts) { |
|||
Date date = new Date(ts); |
|||
Calendar c = Calendar.getInstance(); |
|||
c.setTimeZone(tz); |
|||
c.setTime(date); |
|||
return c.get(Calendar.MINUTE); |
|||
} |
|||
|
|||
public static boolean isHoliday(long ts) { |
|||
Date date = new Date(ts); |
|||
Calendar c = Calendar.getInstance(); |
|||
c.setTime(date); |
|||
int dayOfWeek = c.get(Calendar.DAY_OF_WEEK); |
|||
return dayOfWeek == 1 || dayOfWeek == 7; |
|||
} |
|||
|
|||
public static boolean isWorkHour(long ts) { |
|||
Date date = new Date(ts); |
|||
Calendar c = Calendar.getInstance(); |
|||
c.setTime(date); |
|||
int hour = c.get(Calendar.HOUR_OF_DAY); |
|||
|
|||
return hour > 8 && hour < 18; |
|||
} |
|||
|
|||
public static boolean isNightHour(long ts) { |
|||
Date date = new Date(ts); |
|||
Calendar c = Calendar.getInstance(); |
|||
c.setTime(date); |
|||
int hour = c.get(Calendar.HOUR_OF_DAY); |
|||
|
|||
return hour < 6 || hour >= 22; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import lombok.Getter; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.math.RoundingMode; |
|||
|
|||
import static org.thingsboard.server.service.solutions.data.values.GeneratorTools.randomDouble; |
|||
|
|||
public abstract class IncDecTelemetryGenerator<T extends IncDecValueStrategyDefinition> extends TelemetryGenerator { |
|||
|
|||
protected final T strategy; |
|||
@Getter |
|||
protected double value; |
|||
protected double endValue; |
|||
|
|||
@SuppressWarnings("unchecked") |
|||
public IncDecTelemetryGenerator(TelemetryProfile telemetryProfile) { |
|||
super(telemetryProfile); |
|||
this.strategy = (T) telemetryProfile.getValueStrategy(); |
|||
this.value = getRandomStartValue(); |
|||
this.endValue = getRandomEndValue(); |
|||
} |
|||
|
|||
public void setValue(double value) { |
|||
this.value = value; |
|||
this.endValue = getRandomEndValue(); |
|||
} |
|||
|
|||
protected void put(ObjectNode values, double value) { |
|||
if (strategy.getPrecision() == 0) { |
|||
values.put(key, (int) value); |
|||
} else { |
|||
values.put(key, BigDecimal.valueOf(value) |
|||
.setScale(strategy.getPrecision(), RoundingMode.HALF_UP) |
|||
.doubleValue()); |
|||
} |
|||
} |
|||
|
|||
public double getRandomStartValue() { |
|||
return randomDouble(strategy.getMinStartValue(), strategy.getMaxStartValue()); |
|||
} |
|||
|
|||
public double getRandomEndValue() { |
|||
return randomDouble(strategy.getMinEndValue(), strategy.getMaxEndValue()); |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public abstract class IncDecValueStrategyDefinition implements ValueStrategyDefinition { |
|||
|
|||
private int precision; |
|||
private double minStartValue; |
|||
private double maxStartValue; |
|||
private double minEndValue; |
|||
private double maxEndValue; |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
import static org.thingsboard.server.service.solutions.data.values.GeneratorTools.randomDouble; |
|||
|
|||
public class IncrementTelemetryGenerator extends IncDecTelemetryGenerator<IncrementValueStrategyDefinition> { |
|||
|
|||
public IncrementTelemetryGenerator(TelemetryProfile telemetryProfile) { |
|||
super(telemetryProfile); |
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
double step = randomDouble(strategy.getMinIncrement(), strategy.getMaxIncrement()); |
|||
double newValue = value + step; |
|||
value = Math.min(newValue, endValue); |
|||
put(values, value); |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class IncrementValueStrategyDefinition extends IncDecValueStrategyDefinition { |
|||
|
|||
private double minIncrement; |
|||
private double maxIncrement; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.INCREMENT; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.math.RoundingMode; |
|||
|
|||
import static org.thingsboard.server.service.solutions.data.values.GeneratorTools.getMultiplier; |
|||
import static org.thingsboard.server.service.solutions.data.values.GeneratorTools.randomDouble; |
|||
|
|||
public class NaturalTelemetryGenerator extends TelemetryGenerator { |
|||
|
|||
private final NaturalValueStrategyDefinition strategy; |
|||
@Getter @Setter |
|||
private double value; |
|||
private boolean isIncrement; |
|||
private double lowValue; |
|||
private double highValue; |
|||
|
|||
public NaturalTelemetryGenerator(TelemetryProfile telemetryProfile) { |
|||
super(telemetryProfile); |
|||
this.strategy = (NaturalValueStrategyDefinition) telemetryProfile.getValueStrategy(); |
|||
this.value = getRandomStartValue(); |
|||
this.lowValue = getRandomLowValue(); |
|||
this.highValue = getRandomHighValue(); |
|||
isIncrement = !strategy.isDecrementOnStart(); |
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
|
|||
double multiplier = getMultiplier(ts, strategy.getHolidayMultiplier(), strategy.getWorkHoursMultiplier(), strategy.getNightHoursMultiplier()); |
|||
|
|||
if (isIncrement) { |
|||
double step = randomDouble(strategy.getMinIncrement(), strategy.getMaxIncrement()); |
|||
value += step * multiplier; |
|||
if (value > highValue) { |
|||
value = highValue; |
|||
highValue = getRandomHighValue(); |
|||
isIncrement = false; |
|||
} |
|||
} else { |
|||
double step = randomDouble(strategy.getMinDecrement(), strategy.getMaxDecrement()); |
|||
value -= step * multiplier; |
|||
if (value < lowValue) { |
|||
value = lowValue; |
|||
lowValue = getRandomLowValue(); |
|||
isIncrement = true; |
|||
} |
|||
} |
|||
|
|||
put(values, value); |
|||
} |
|||
|
|||
private void put(ObjectNode values, double value) { |
|||
if (strategy.getPrecision() == 0) { |
|||
values.put(key, (int) value); |
|||
} else { |
|||
values.put(key, BigDecimal.valueOf(value) |
|||
.setScale(strategy.getPrecision(), RoundingMode.HALF_UP) |
|||
.doubleValue()); |
|||
} |
|||
} |
|||
|
|||
public double getRandomStartValue() { |
|||
return randomDouble(strategy.getMinStartValue(), strategy.getMaxStartValue()); |
|||
} |
|||
|
|||
public double getRandomLowValue() { |
|||
return randomDouble(strategy.getMinLowValue(), strategy.getMaxLowValue()); |
|||
} |
|||
|
|||
public double getRandomHighValue() { |
|||
return randomDouble(strategy.getMinHighValue(), strategy.getMaxHighValue()); |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class NaturalValueStrategyDefinition implements ValueStrategyDefinition { |
|||
private int precision; |
|||
private double minStartValue; |
|||
private double maxStartValue; |
|||
private double minLowValue; |
|||
private double maxLowValue; |
|||
private double minHighValue; |
|||
private double maxHighValue; |
|||
private double minIncrement; |
|||
private double maxIncrement; |
|||
private double minDecrement; |
|||
private double maxDecrement; |
|||
private double holidayMultiplier; |
|||
private double workHoursMultiplier; |
|||
private double nightHoursMultiplier; |
|||
private boolean decrementOnStart; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.NATURAL; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import lombok.Data; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class ScheduleValueStrategyDefinition implements ValueStrategyDefinition { |
|||
|
|||
private List<ValueStrategySchedule> schedule; |
|||
private String timeZone; |
|||
private ValueStrategyDefinition defaultDefinition; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.SCHEDULE; |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
import java.util.LinkedHashMap; |
|||
import java.util.Map; |
|||
import java.util.TimeZone; |
|||
|
|||
public class ScheduleValueStrategyGenerator extends TelemetryGenerator { |
|||
|
|||
private TelemetryGenerator defaultGenerator; |
|||
private TimeZone timeZone; |
|||
private Map<ValueStrategySchedule, TelemetryGenerator> scheduleGenerators; |
|||
private TelemetryGenerator prevGenerator; |
|||
|
|||
public ScheduleValueStrategyGenerator(TelemetryProfile tp) { |
|||
super(tp); |
|||
var def = (ScheduleValueStrategyDefinition) tp.getValueStrategy(); |
|||
timeZone = TimeZone.getTimeZone(def.getTimeZone()); |
|||
defaultGenerator = TelemetryGeneratorFactory.create(new TelemetryProfile(tp.getKey(), def.getDefaultDefinition())); |
|||
scheduleGenerators = new LinkedHashMap<>(); |
|||
for (ValueStrategySchedule scheduleItem : def.getSchedule()) { |
|||
scheduleGenerators.put(scheduleItem, TelemetryGeneratorFactory.create(new TelemetryProfile(tp.getKey(), scheduleItem.getDefinition()))); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
int hour = GeneratorTools.getHour(timeZone, ts); |
|||
int minute = GeneratorTools.getMinute(timeZone, ts); |
|||
TelemetryGenerator generator = scheduleGenerators.entrySet().stream().filter(pair -> { |
|||
var schedule = pair.getKey(); |
|||
if (hour == schedule.getStartHour() && hour == schedule.getEndHour()) { |
|||
return schedule.getStartMinute() <= minute && minute <= schedule.getEndMinute(); |
|||
} else if (hour == schedule.getStartHour() && hour < schedule.getEndHour()) { |
|||
return schedule.getStartMinute() <= minute; |
|||
} else if (hour > schedule.getStartHour() && hour < schedule.getEndHour()) { |
|||
return true; |
|||
} else if (hour > schedule.getStartHour() && hour == schedule.getEndHour()) { |
|||
return minute <= schedule.getEndHour(); |
|||
} else { |
|||
return false; |
|||
} |
|||
}).map(Map.Entry::getValue).findFirst().orElse(defaultGenerator); |
|||
|
|||
if (prevGenerator != null && !prevGenerator.equals(generator)) { |
|||
generator.setValue(prevGenerator.getValue()); |
|||
} |
|||
|
|||
generator.addValue(ts, values); |
|||
|
|||
prevGenerator = generator; |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import lombok.Data; |
|||
|
|||
|
|||
@Data |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public class SequenceValueStrategyDefinition implements ValueStrategyDefinition { |
|||
private boolean random; |
|||
private ObjectNode telemetry; |
|||
|
|||
@Override |
|||
public ValueStrategyDefinitionType getStrategyType() { |
|||
return ValueStrategyDefinitionType.SEQUENCE; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
import java.util.Random; |
|||
|
|||
public class SequenceValueStrategyGenerator extends TelemetryGenerator { |
|||
|
|||
private final SequenceValueStrategyDefinition strategy; |
|||
private int max; |
|||
private int index; |
|||
|
|||
public SequenceValueStrategyGenerator(TelemetryProfile telemetryProfile) { |
|||
super(telemetryProfile); |
|||
this.strategy = (SequenceValueStrategyDefinition) telemetryProfile.getValueStrategy(); |
|||
max = strategy.getTelemetry().fields().next().getValue().size() - 1; |
|||
index = strategy.isRandom() ? new Random().nextInt(max + 1) : 0; |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public void addValue(long ts, ObjectNode values) { |
|||
strategy.getTelemetry().fields().forEachRemaining(entry -> { |
|||
String key = entry.getKey(); |
|||
values.set(key, entry.getValue().get(index)); |
|||
}); |
|||
index++; |
|||
if (index > max) { |
|||
index = 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
@Data |
|||
public abstract class TelemetryGenerator { |
|||
protected final TelemetryProfile profile; |
|||
protected final String key; |
|||
|
|||
public TelemetryGenerator(TelemetryProfile telemetryProfile) { |
|||
this.profile = telemetryProfile; |
|||
this.key = telemetryProfile.getKey(); |
|||
} |
|||
|
|||
public double getValue() { |
|||
throw new RuntimeException("Not supported"); |
|||
} |
|||
|
|||
public void setValue(double value) { |
|||
throw new RuntimeException("Not supported"); |
|||
} |
|||
|
|||
public abstract void addValue(long ts, ObjectNode values); |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
|
|||
import org.thingsboard.server.service.solutions.data.definition.TelemetryProfile; |
|||
|
|||
public class TelemetryGeneratorFactory { |
|||
|
|||
public static TelemetryGenerator create(TelemetryProfile tp) { |
|||
switch (tp.getValueStrategy().getStrategyType()) { |
|||
case COUNTER: |
|||
return new CounterTelemetryGenerator(tp); |
|||
case NATURAL: |
|||
return new NaturalTelemetryGenerator(tp); |
|||
case EVENT: |
|||
return new EventTelemetryGenerator(tp); |
|||
case CONSTANT: |
|||
return new ConstantTelemetryGenerator(tp); |
|||
case SEQUENCE: |
|||
return new SequenceValueStrategyGenerator(tp); |
|||
case COMPOSITE: |
|||
return new CompositeValueStrategyGenerator(tp); |
|||
case SCHEDULE: |
|||
return new ScheduleValueStrategyGenerator(tp); |
|||
case INCREMENT: |
|||
return new IncrementTelemetryGenerator(tp); |
|||
case DECREMENT: |
|||
return new DecrementTelemetryGenerator(tp); |
|||
default: |
|||
throw new RuntimeException("Not supported!"); |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
|
|||
@JsonTypeInfo( |
|||
use = JsonTypeInfo.Id.NAME, |
|||
include = JsonTypeInfo.As.PROPERTY, |
|||
property = "type") |
|||
@JsonSubTypes({ |
|||
@JsonSubTypes.Type(value = CounterValueStrategyDefinition.class, name = "counter"), |
|||
@JsonSubTypes.Type(value = NaturalValueStrategyDefinition.class, name = "natural"), |
|||
@JsonSubTypes.Type(value = EventValueStrategyDefinition.class, name = "event"), |
|||
@JsonSubTypes.Type(value = SequenceValueStrategyDefinition.class, name = "sequence"), |
|||
@JsonSubTypes.Type(value = ConstantValueStrategyDefinition.class, name = "constant"), |
|||
@JsonSubTypes.Type(value = CompositeValueStrategyDefinition.class, name = "composite"), |
|||
@JsonSubTypes.Type(value = ScheduleValueStrategyDefinition.class, name = "schedule"), |
|||
@JsonSubTypes.Type(value = IncrementValueStrategyDefinition.class, name = "inc"), |
|||
@JsonSubTypes.Type(value = DecrementValueStrategyDefinition.class, name = "dec")}) |
|||
public interface ValueStrategyDefinition { |
|||
|
|||
ValueStrategyDefinitionType getStrategyType(); |
|||
|
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
public enum ValueStrategyDefinitionType { |
|||
|
|||
COUNTER, NATURAL, EVENT, SEQUENCE, CONSTANT, COMPOSITE, SCHEDULE, INCREMENT, DECREMENT; |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.solutions.data.values; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class ValueStrategySchedule { |
|||
|
|||
private int startHour; |
|||
private int startMinute; |
|||
|
|||
private int endHour; |
|||
private int endMinute; |
|||
|
|||
private ValueStrategyDefinition definition; |
|||
|
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.dao.device; |
|||
|
|||
public record DockerComposeParams(boolean includeVersion, String containerName, boolean includePortBindings, |
|||
boolean includeExtraHosts, boolean includeVolumesBind, |
|||
boolean includeVolumesDeclaration) { |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.id; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonCreator; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
public class IotHubInstalledItemId extends UUIDBased { |
|||
|
|||
@JsonCreator |
|||
public IotHubInstalledItemId(@JsonProperty("id") UUID id) { |
|||
super(id); |
|||
} |
|||
|
|||
public static IotHubInstalledItemId fromString(String id) { |
|||
return new IotHubInstalledItemId(UUID.fromString(id)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
@Data |
|||
public class CalculatedFieldInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
|||
|
|||
private CalculatedFieldId calculatedFieldId; |
|||
private EntityId entityId; |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
|
|||
@Data |
|||
public class DashboardInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
|||
|
|||
private DashboardId dashboardId; |
|||
|
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
|
|||
@Data |
|||
public class DeviceInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
|||
|
|||
private List<EntityId> createdEntityIds; |
|||
private DashboardId dashboardId; |
|||
private String selectedInstallMethod; |
|||
private Map<String, JsonNode> installState; |
|||
|
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.thingsboard.server.common.data.BaseData; |
|||
import org.thingsboard.server.common.data.HasTenantId; |
|||
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
public class IotHubInstalledItem extends BaseData<IotHubInstalledItemId> implements HasTenantId { |
|||
|
|||
private TenantId tenantId; |
|||
private UUID itemId; |
|||
private UUID itemVersionId; |
|||
private String itemName; |
|||
private String itemType; |
|||
private String version; |
|||
private IotHubInstalledItemDescriptor descriptor; |
|||
|
|||
public IotHubInstalledItem() { |
|||
super(); |
|||
} |
|||
|
|||
public IotHubInstalledItem(IotHubInstalledItemId id) { |
|||
super(id); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonSubTypes; |
|||
import com.fasterxml.jackson.annotation.JsonSubTypes.Type; |
|||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
|||
|
|||
import java.io.Serializable; |
|||
|
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
@JsonTypeInfo( |
|||
use = JsonTypeInfo.Id.NAME, |
|||
property = "type") |
|||
@JsonSubTypes({ |
|||
@Type(name = "WIDGET", value = WidgetInstalledItemDescriptor.class), |
|||
@Type(name = "DASHBOARD", value = DashboardInstalledItemDescriptor.class), |
|||
@Type(name = "CALCULATED_FIELD", value = CalculatedFieldInstalledItemDescriptor.class), |
|||
@Type(name = "RULE_CHAIN", value = RuleChainInstalledItemDescriptor.class), |
|||
@Type(name = "DEVICE", value = DeviceInstalledItemDescriptor.class), |
|||
@Type(name = "SOLUTION_TEMPLATE", value = SolutionTemplateInstalledItemDescriptor.class) |
|||
}) |
|||
public interface IotHubInstalledItemDescriptor extends Serializable { |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.RuleChainId; |
|||
|
|||
@Data |
|||
public class RuleChainInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
|||
|
|||
private RuleChainId ruleChainId; |
|||
|
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
import org.thingsboard.server.common.data.id.EntityId; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
public class SolutionTemplateInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
|||
|
|||
private List<EntityId> createdEntityIds; |
|||
private List<String> tenantTelemetryKeys; |
|||
private List<String> tenantAttributeKeys; |
|||
private DashboardId dashboardId; |
|||
private CustomerId publicId; |
|||
private boolean mainDashboardPublic; |
|||
private String details; |
|||
|
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import lombok.Data; |
|||
import org.thingsboard.server.common.data.id.WidgetTypeId; |
|||
|
|||
@Data |
|||
public class WidgetInstalledItemDescriptor implements IotHubInstalledItemDescriptor { |
|||
|
|||
private WidgetTypeId widgetTypeId; |
|||
|
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.data.iot_hub; |
|||
|
|||
import com.fasterxml.jackson.databind.JsonNode; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import org.junit.jupiter.api.Test; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.id.DashboardId; |
|||
|
|||
import java.util.UUID; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
|
|||
class DeviceInstalledItemDescriptorTest { |
|||
|
|||
private static final ObjectMapper mapper = new ObjectMapper(); |
|||
|
|||
@Test |
|||
void deserializeFromFrontendPayload() throws Exception { |
|||
// Exact payload the frontend sends after the fix
|
|||
String json = """ |
|||
{ |
|||
"type": "DEVICE", |
|||
"createdEntityIds": [ |
|||
{"entityType": "DEVICE_PROFILE", "id": "3eb9c330-2e57-11f1-9334-a385172e8e7d"}, |
|||
{"entityType": "DEVICE", "id": "8a90d5e0-2e5d-11f1-a802-c35a9af2ebde"}, |
|||
{"entityType": "DASHBOARD", "id": "8bc229f0-2e5d-11f1-a802-c35a9af2ebde"} |
|||
], |
|||
"dashboardId": {"entityType": "DASHBOARD", "id": "8bc229f0-2e5d-11f1-a802-c35a9af2ebde"} |
|||
} |
|||
"""; |
|||
|
|||
// Deserialize the same way the controller does (JsonNode → treeToValue)
|
|||
JsonNode node = mapper.readTree(json); |
|||
DeviceInstalledItemDescriptor descriptor = mapper.treeToValue(node, DeviceInstalledItemDescriptor.class); |
|||
|
|||
assertThat(descriptor).isNotNull(); |
|||
assertThat(descriptor.getCreatedEntityIds()).hasSize(3); |
|||
assertThat(descriptor.getCreatedEntityIds().get(0).getEntityType()).isEqualTo(EntityType.DEVICE_PROFILE); |
|||
assertThat(descriptor.getCreatedEntityIds().get(1).getEntityType()).isEqualTo(EntityType.DEVICE); |
|||
assertThat(descriptor.getCreatedEntityIds().get(2).getEntityType()).isEqualTo(EntityType.DASHBOARD); |
|||
assertThat(descriptor.getDashboardId()).isNotNull(); |
|||
assertThat(descriptor.getDashboardId().getId()).isEqualTo(UUID.fromString("8bc229f0-2e5d-11f1-a802-c35a9af2ebde")); |
|||
} |
|||
|
|||
@Test |
|||
void roundTripSerialization() throws Exception { |
|||
DeviceInstalledItemDescriptor original = new DeviceInstalledItemDescriptor(); |
|||
DashboardId dashboardId = new DashboardId(UUID.randomUUID()); |
|||
original.setDashboardId(dashboardId); |
|||
original.setCreatedEntityIds(java.util.List.of(dashboardId)); |
|||
|
|||
String json = mapper.writeValueAsString(original); |
|||
JsonNode node = mapper.readTree(json); |
|||
DeviceInstalledItemDescriptor deserialized = mapper.treeToValue(node, DeviceInstalledItemDescriptor.class); |
|||
|
|||
assertThat(deserialized.getDashboardId().getId()).isEqualTo(original.getDashboardId().getId()); |
|||
assertThat(deserialized.getCreatedEntityIds()).hasSize(1); |
|||
assertThat(deserialized.getCreatedEntityIds().get(0).getId()).isEqualTo(dashboardId.getId()); |
|||
} |
|||
|
|||
@Test |
|||
void deserializeWithInstallState() throws Exception { |
|||
String json = """ |
|||
{ |
|||
"type": "DEVICE", |
|||
"createdEntityIds": [ |
|||
{"entityType": "DEVICE_PROFILE", "id": "3eb9c330-2e57-11f1-9334-a385172e8e7d"}, |
|||
{"entityType": "DEVICE", "id": "8a90d5e0-2e5d-11f1-a802-c35a9af2ebde"}, |
|||
{"entityType": "DASHBOARD", "id": "8bc229f0-2e5d-11f1-a802-c35a9af2ebde"} |
|||
], |
|||
"dashboardId": {"entityType": "DASHBOARD", "id": "8bc229f0-2e5d-11f1-a802-c35a9af2ebde"}, |
|||
"selectedInstallMethod": "DIRECT_MQTT", |
|||
"installState": { |
|||
"Configuration": { |
|||
"formValues": {"deviceName": "ESP32 Dev Kit", "wifiSsid": "MyWiFi", "wifiPassword": "secret"} |
|||
}, |
|||
"ESP32 Dev Kit": { |
|||
"entityOutput": {"id": "8a90d5e0-2e5d-11f1-a802-c35a9af2ebde", "name": "ESP32 Dev Kit", "token": "abc123", "url": "/entities/devices/8a90d5e0-2e5d-11f1-a802-c35a9af2ebde"} |
|||
}, |
|||
"ESP32 Monitor": { |
|||
"entityOutput": {"id": "8bc229f0-2e5d-11f1-a802-c35a9af2ebde", "name": "ESP32 Monitor", "url": "/dashboards/8bc229f0-2e5d-11f1-a802-c35a9af2ebde"} |
|||
} |
|||
} |
|||
} |
|||
"""; |
|||
|
|||
JsonNode node = mapper.readTree(json); |
|||
DeviceInstalledItemDescriptor descriptor = mapper.treeToValue(node, DeviceInstalledItemDescriptor.class); |
|||
|
|||
assertThat(descriptor).isNotNull(); |
|||
assertThat(descriptor.getSelectedInstallMethod()).isEqualTo("DIRECT_MQTT"); |
|||
assertThat(descriptor.getInstallState()).isNotNull(); |
|||
assertThat(descriptor.getInstallState()).hasSize(3); |
|||
|
|||
// Verify form values
|
|||
JsonNode configState = descriptor.getInstallState().get("Configuration"); |
|||
assertThat(configState).isNotNull(); |
|||
assertThat(configState.get("formValues").get("deviceName").asText()).isEqualTo("ESP32 Dev Kit"); |
|||
assertThat(configState.get("formValues").get("wifiSsid").asText()).isEqualTo("MyWiFi"); |
|||
|
|||
// Verify entity output
|
|||
JsonNode deviceState = descriptor.getInstallState().get("ESP32 Dev Kit"); |
|||
assertThat(deviceState).isNotNull(); |
|||
assertThat(deviceState.get("entityOutput").get("token").asText()).isEqualTo("abc123"); |
|||
|
|||
// Verify round-trip
|
|||
String serialized = mapper.writeValueAsString(descriptor); |
|||
JsonNode roundTripped = mapper.readTree(serialized); |
|||
DeviceInstalledItemDescriptor deserialized = mapper.treeToValue(roundTripped, DeviceInstalledItemDescriptor.class); |
|||
assertThat(deserialized.getSelectedInstallMethod()).isEqualTo("DIRECT_MQTT"); |
|||
assertThat(deserialized.getInstallState()).hasSize(3); |
|||
assertThat(deserialized.getInstallState().get("Configuration").get("formValues").get("deviceName").asText()).isEqualTo("ESP32 Dev Kit"); |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.dao.iot_hub; |
|||
|
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
import org.thingsboard.server.dao.Dao; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
public interface IotHubInstalledItemDao extends Dao<IotHubInstalledItem> { |
|||
|
|||
PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, List<String> itemTypes, UUID itemId, PageLink pageLink); |
|||
|
|||
List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId); |
|||
|
|||
long countByTenantId(TenantId tenantId, String itemType); |
|||
|
|||
Map<UUID, Long> findInstalledItemCounts(TenantId tenantId, String itemType); |
|||
|
|||
void deleteByTenantId(TenantId tenantId); |
|||
|
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.dao.iot_hub; |
|||
|
|||
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
public interface IotHubInstalledItemService { |
|||
|
|||
IotHubInstalledItem save(TenantId tenantId, IotHubInstalledItem item); |
|||
|
|||
IotHubInstalledItem findById(TenantId tenantId, IotHubInstalledItemId id); |
|||
|
|||
PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, List<String> itemTypes, UUID itemId, PageLink pageLink); |
|||
|
|||
List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId); |
|||
|
|||
long countByTenantId(TenantId tenantId, String itemType); |
|||
|
|||
Map<UUID, Long> findInstalledItemCounts(TenantId tenantId, String itemType); |
|||
|
|||
void deleteById(TenantId tenantId, IotHubInstalledItemId id); |
|||
|
|||
void deleteByTenantId(TenantId tenantId); |
|||
|
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.dao.iot_hub; |
|||
|
|||
import lombok.RequiredArgsConstructor; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.page.PageLink; |
|||
|
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.UUID; |
|||
|
|||
@Service |
|||
@RequiredArgsConstructor |
|||
@Slf4j |
|||
class IotHubInstalledItemServiceImpl implements IotHubInstalledItemService { |
|||
|
|||
private final IotHubInstalledItemDao iotHubInstalledItemDao; |
|||
|
|||
@Override |
|||
public IotHubInstalledItem save(TenantId tenantId, IotHubInstalledItem item) { |
|||
log.debug("[{}] Saving IoT Hub installed item: {}", tenantId, item); |
|||
return iotHubInstalledItemDao.save(tenantId, item); |
|||
} |
|||
|
|||
@Override |
|||
public IotHubInstalledItem findById(TenantId tenantId, IotHubInstalledItemId id) { |
|||
return iotHubInstalledItemDao.findById(tenantId, id.getId()); |
|||
} |
|||
|
|||
@Override |
|||
public PageData<IotHubInstalledItem> findByTenantId(TenantId tenantId, List<String> itemTypes, UUID itemId, PageLink pageLink) { |
|||
return iotHubInstalledItemDao.findByTenantId(tenantId, itemTypes, itemId, pageLink); |
|||
} |
|||
|
|||
@Override |
|||
public List<UUID> findInstalledItemIdsByTenantId(TenantId tenantId) { |
|||
return iotHubInstalledItemDao.findInstalledItemIdsByTenantId(tenantId); |
|||
} |
|||
|
|||
@Override |
|||
public long countByTenantId(TenantId tenantId, String itemType) { |
|||
return iotHubInstalledItemDao.countByTenantId(tenantId, itemType); |
|||
} |
|||
|
|||
@Override |
|||
public Map<UUID, Long> findInstalledItemCounts(TenantId tenantId, String itemType) { |
|||
return iotHubInstalledItemDao.findInstalledItemCounts(tenantId, itemType); |
|||
} |
|||
|
|||
@Override |
|||
public void deleteById(TenantId tenantId, IotHubInstalledItemId id) { |
|||
iotHubInstalledItemDao.removeById(tenantId, id.getId()); |
|||
} |
|||
|
|||
@Override |
|||
public void deleteByTenantId(TenantId tenantId) { |
|||
iotHubInstalledItemDao.deleteByTenantId(tenantId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,110 @@ |
|||
/** |
|||
* Copyright © 2016-2026 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.dao.model.sql; |
|||
|
|||
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType; |
|||
import jakarta.persistence.Column; |
|||
import jakarta.persistence.Entity; |
|||
import jakarta.persistence.Table; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import lombok.ToString; |
|||
import org.hibernate.annotations.Type; |
|||
import org.hibernate.proxy.HibernateProxy; |
|||
import org.thingsboard.server.common.data.id.IotHubInstalledItemId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItem; |
|||
import org.thingsboard.server.common.data.iot_hub.IotHubInstalledItemDescriptor; |
|||
import org.thingsboard.server.dao.model.BaseSqlEntity; |
|||
import org.thingsboard.server.dao.model.ModelConstants; |
|||
|
|||
import java.util.Objects; |
|||
import java.util.UUID; |
|||
|
|||
@Getter |
|||
@Setter |
|||
@ToString |
|||
@Entity |
|||
@Table(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_TABLE_NAME) |
|||
public class IotHubInstalledItemEntity extends BaseSqlEntity<IotHubInstalledItem> { |
|||
|
|||
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_TENANT_ID_COLUMN, nullable = false, columnDefinition = "UUID") |
|||
private UUID tenantId; |
|||
|
|||
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_ITEM_ID_COLUMN, nullable = false, columnDefinition = "UUID") |
|||
private UUID itemId; |
|||
|
|||
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_ITEM_VERSION_ID_COLUMN, nullable = false, columnDefinition = "UUID") |
|||
private UUID itemVersionId; |
|||
|
|||
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_ITEM_NAME_COLUMN, nullable = false) |
|||
private String itemName; |
|||
|
|||
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_ITEM_TYPE_COLUMN, nullable = false) |
|||
private String itemType; |
|||
|
|||
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_VERSION_COLUMN, nullable = false) |
|||
private String version; |
|||
|
|||
@Type(JsonBinaryType.class) |
|||
@Column(name = ModelConstants.IOT_HUB_INSTALLED_ITEM_DESCRIPTOR_COLUMN, nullable = false, columnDefinition = "JSONB") |
|||
private IotHubInstalledItemDescriptor descriptor; |
|||
|
|||
public IotHubInstalledItemEntity() { |
|||
} |
|||
|
|||
public IotHubInstalledItemEntity(IotHubInstalledItem item) { |
|||
super(item); |
|||
tenantId = getTenantUuid(item.getTenantId()); |
|||
itemId = item.getItemId(); |
|||
itemVersionId = item.getItemVersionId(); |
|||
itemName = item.getItemName(); |
|||
itemType = item.getItemType(); |
|||
version = item.getVersion(); |
|||
descriptor = item.getDescriptor(); |
|||
} |
|||
|
|||
@Override |
|||
public IotHubInstalledItem toData() { |
|||
var item = new IotHubInstalledItem(new IotHubInstalledItemId(id)); |
|||
item.setCreatedTime(createdTime); |
|||
item.setTenantId(TenantId.fromUUID(tenantId)); |
|||
item.setItemId(itemId); |
|||
item.setItemVersionId(itemVersionId); |
|||
item.setItemName(itemName); |
|||
item.setItemType(itemType); |
|||
item.setVersion(version); |
|||
item.setDescriptor(descriptor); |
|||
return item; |
|||
} |
|||
|
|||
@Override |
|||
public final boolean equals(Object o) { |
|||
if (this == o) return true; |
|||
if (o == null) return false; |
|||
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); |
|||
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); |
|||
if (thisEffectiveClass != oEffectiveClass) return false; |
|||
IotHubInstalledItemEntity that = (IotHubInstalledItemEntity) o; |
|||
return getId() != null && Objects.equals(getId(), that.getId()); |
|||
} |
|||
|
|||
@Override |
|||
public final int hashCode() { |
|||
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); |
|||
} |
|||
|
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue