CalculatedField functionality support for Edge
- added test
This commit is contained in:
parent
d45cbcfcbd
commit
93948bf64b
@ -565,7 +565,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
|
|||||||
protected Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception {
|
protected Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception {
|
||||||
// create device and assign to edge
|
// create device and assign to edge
|
||||||
Device savedDevice = saveDevice(StringUtils.randomAlphanumeric(15), thermostatDeviceProfile.getName());
|
Device savedDevice = saveDevice(StringUtils.randomAlphanumeric(15), thermostatDeviceProfile.getName());
|
||||||
edgeImitator.expectMessageAmount(2); // device and device profile messages
|
edgeImitator.expectMessageAmount(3); // device and device profile messages and device credentials
|
||||||
doPost("/api/edge/" + edge.getUuidId()
|
doPost("/api/edge/" + edge.getUuidId()
|
||||||
+ "/device/" + savedDevice.getUuidId(), Device.class);
|
+ "/device/" + savedDevice.getUuidId(), Device.class);
|
||||||
Assert.assertTrue(edgeImitator.waitForMessages());
|
Assert.assertTrue(edgeImitator.waitForMessages());
|
||||||
|
|||||||
@ -0,0 +1,228 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2025 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.thingsboard.server.edge;
|
||||||
|
|
||||||
|
import com.datastax.oss.driver.api.core.uuid.Uuids;
|
||||||
|
import com.google.protobuf.AbstractMessage;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
|
import org.thingsboard.server.common.data.Device;
|
||||||
|
import org.thingsboard.server.common.data.cf.CalculatedField;
|
||||||
|
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.Argument;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.Output;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.OutputType;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
|
||||||
|
import org.thingsboard.server.common.data.debug.DebugSettings;
|
||||||
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
|
import org.thingsboard.server.dao.service.DaoSqlTest;
|
||||||
|
import org.thingsboard.server.gen.edge.v1.CalculatedFieldUpdateMsg;
|
||||||
|
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
|
||||||
|
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
|
||||||
|
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@DaoSqlTest
|
||||||
|
public class CalculatedFieldEdgeTest extends AbstractEdgeTest {
|
||||||
|
private static final String DEFAULT_CF_NAME = "Edge Test CalculatedField";
|
||||||
|
private static final String UPDATED_CF_NAME = "Updated Edge Test CalculatedField";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculatedField_create_update_delete() throws Exception {
|
||||||
|
Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
|
||||||
|
|
||||||
|
// create calculatedField
|
||||||
|
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
|
||||||
|
CalculatedField calculatedField = createSimpleCalculatedField(savedDevice.getId(), config);
|
||||||
|
|
||||||
|
edgeImitator.expectMessageAmount(1);
|
||||||
|
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
|
||||||
|
Assert.assertTrue(edgeImitator.waitForMessages());
|
||||||
|
|
||||||
|
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
|
||||||
|
Assert.assertTrue(latestMessage instanceof CalculatedFieldUpdateMsg);
|
||||||
|
CalculatedFieldUpdateMsg calculatedFieldUpdateMsg = (CalculatedFieldUpdateMsg) latestMessage;
|
||||||
|
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, calculatedFieldUpdateMsg.getMsgType());
|
||||||
|
Assert.assertEquals(savedCalculatedField.getUuidId().getMostSignificantBits(), calculatedFieldUpdateMsg.getIdMSB());
|
||||||
|
Assert.assertEquals(savedCalculatedField.getUuidId().getLeastSignificantBits(), calculatedFieldUpdateMsg.getIdLSB());
|
||||||
|
CalculatedField calculatedFieldFromMsg = JacksonUtil.fromString(calculatedFieldUpdateMsg.getEntity(), CalculatedField.class, true);
|
||||||
|
Assert.assertNotNull(calculatedFieldFromMsg);
|
||||||
|
|
||||||
|
Assert.assertEquals(DEFAULT_CF_NAME, calculatedFieldFromMsg.getName());
|
||||||
|
Assert.assertEquals(savedDevice.getId(), calculatedFieldFromMsg.getEntityId());
|
||||||
|
Assert.assertEquals(config, calculatedFieldFromMsg.getConfiguration());
|
||||||
|
|
||||||
|
// update calculatedField
|
||||||
|
edgeImitator.expectMessageAmount(1);
|
||||||
|
savedCalculatedField.setName(UPDATED_CF_NAME);
|
||||||
|
savedCalculatedField = doPost("/api/calculatedField", savedCalculatedField, CalculatedField.class);
|
||||||
|
Assert.assertTrue(edgeImitator.waitForMessages());
|
||||||
|
latestMessage = edgeImitator.getLatestMessage();
|
||||||
|
Assert.assertTrue(latestMessage instanceof CalculatedFieldUpdateMsg);
|
||||||
|
calculatedFieldUpdateMsg = (CalculatedFieldUpdateMsg) latestMessage;
|
||||||
|
calculatedFieldFromMsg = JacksonUtil.fromString(calculatedFieldUpdateMsg.getEntity(), CalculatedField.class, true);
|
||||||
|
Assert.assertNotNull(calculatedFieldFromMsg);
|
||||||
|
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, calculatedFieldUpdateMsg.getMsgType());
|
||||||
|
Assert.assertEquals(UPDATED_CF_NAME, calculatedFieldFromMsg.getName());
|
||||||
|
|
||||||
|
// delete calculatedField
|
||||||
|
edgeImitator.expectMessageAmount(1);
|
||||||
|
doDelete("/api/calculatedField/" + savedCalculatedField.getUuidId())
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
Assert.assertTrue(edgeImitator.waitForMessages());
|
||||||
|
latestMessage = edgeImitator.getLatestMessage();
|
||||||
|
Assert.assertTrue(latestMessage instanceof CalculatedFieldUpdateMsg);
|
||||||
|
calculatedFieldUpdateMsg = (CalculatedFieldUpdateMsg) latestMessage;
|
||||||
|
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, calculatedFieldUpdateMsg.getMsgType());
|
||||||
|
Assert.assertEquals(savedCalculatedField.getUuidId().getMostSignificantBits(), calculatedFieldUpdateMsg.getIdMSB());
|
||||||
|
Assert.assertEquals(savedCalculatedField.getUuidId().getLeastSignificantBits(), calculatedFieldUpdateMsg.getIdLSB());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendCalculatedFieldToCloud() throws Exception {
|
||||||
|
Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
|
||||||
|
|
||||||
|
// create calculatedField
|
||||||
|
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
|
||||||
|
CalculatedField calculatedField = createSimpleCalculatedField(savedDevice.getId(), config);
|
||||||
|
UUID uuid = Uuids.timeBased();
|
||||||
|
UplinkMsg uplinkMsg = getUplinkMsg(uuid, calculatedField, UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
|
||||||
|
|
||||||
|
checkCalculatedFieldOnCloud(uplinkMsg, uuid, calculatedField.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateCalculatedFieldNameOnCloud() throws Exception {
|
||||||
|
Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
|
||||||
|
|
||||||
|
// create calculatedField
|
||||||
|
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
|
||||||
|
CalculatedField calculatedField = createSimpleCalculatedField(savedDevice.getId(), config);
|
||||||
|
UUID uuid = Uuids.timeBased();
|
||||||
|
UplinkMsg uplinkMsg = getUplinkMsg(uuid, calculatedField, UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
|
||||||
|
|
||||||
|
checkCalculatedFieldOnCloud(uplinkMsg, uuid, calculatedField.getName());
|
||||||
|
|
||||||
|
calculatedField.setName(UPDATED_CF_NAME);
|
||||||
|
UplinkMsg updatedUplinkMsg = getUplinkMsg(uuid, calculatedField, UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE);
|
||||||
|
|
||||||
|
checkCalculatedFieldOnCloud(updatedUplinkMsg, uuid, calculatedField.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCalculatedFieldToCloudWithNameThatAlreadyExistsOnCloud() throws Exception {
|
||||||
|
Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
|
||||||
|
|
||||||
|
// create calculatedField
|
||||||
|
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
|
||||||
|
CalculatedField calculatedField = createSimpleCalculatedField(savedDevice.getId(), config);
|
||||||
|
|
||||||
|
edgeImitator.expectMessageAmount(1);
|
||||||
|
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
|
||||||
|
Assert.assertTrue(edgeImitator.waitForMessages());
|
||||||
|
|
||||||
|
UUID uuid = Uuids.timeBased();
|
||||||
|
|
||||||
|
UplinkMsg uplinkMsg = getUplinkMsg(uuid, calculatedField, UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
|
||||||
|
|
||||||
|
edgeImitator.expectResponsesAmount(1);
|
||||||
|
edgeImitator.expectMessageAmount(1);
|
||||||
|
|
||||||
|
edgeImitator.sendUplinkMsg(uplinkMsg);
|
||||||
|
|
||||||
|
Assert.assertTrue(edgeImitator.waitForResponses());
|
||||||
|
Assert.assertTrue(edgeImitator.waitForMessages());
|
||||||
|
|
||||||
|
Optional<CalculatedFieldUpdateMsg> calculatedFieldUpdateMsgOpt = edgeImitator.findMessageByType(CalculatedFieldUpdateMsg.class);
|
||||||
|
Assert.assertTrue(calculatedFieldUpdateMsgOpt.isPresent());
|
||||||
|
CalculatedFieldUpdateMsg latestCalculatedFieldUpdateMsg = calculatedFieldUpdateMsgOpt.get();
|
||||||
|
CalculatedField calculatedFieldFromMsg = JacksonUtil.fromString(latestCalculatedFieldUpdateMsg.getEntity(), CalculatedField.class, true);
|
||||||
|
Assert.assertNotNull(calculatedFieldFromMsg);
|
||||||
|
Assert.assertNotEquals(DEFAULT_CF_NAME, calculatedFieldFromMsg.getName());
|
||||||
|
|
||||||
|
Assert.assertNotEquals(savedCalculatedField.getUuidId(), uuid);
|
||||||
|
|
||||||
|
CalculatedField calculatedFieldFromCloud = doGet("/api/calculatedField/" + uuid, CalculatedField.class);
|
||||||
|
Assert.assertNotNull(calculatedFieldFromCloud);
|
||||||
|
Assert.assertNotEquals(DEFAULT_CF_NAME, calculatedFieldFromCloud.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CalculatedField createSimpleCalculatedField(EntityId entityId, SimpleCalculatedFieldConfiguration config) {
|
||||||
|
CalculatedField calculatedField = new CalculatedField();
|
||||||
|
calculatedField.setEntityId(entityId);
|
||||||
|
calculatedField.setTenantId(tenantId);
|
||||||
|
calculatedField.setType(CalculatedFieldType.SIMPLE);
|
||||||
|
calculatedField.setName(DEFAULT_CF_NAME);
|
||||||
|
calculatedField.setDebugSettings(DebugSettings.all());
|
||||||
|
|
||||||
|
Argument argument = new Argument();
|
||||||
|
ReferencedEntityKey refEntityKey = new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null);
|
||||||
|
argument.setRefEntityKey(refEntityKey);
|
||||||
|
argument.setDefaultValue("12"); // not used because real telemetry value in db is present
|
||||||
|
config.setArguments(Map.of("T", argument));
|
||||||
|
|
||||||
|
config.setExpression("(T * 9/5) + 32");
|
||||||
|
|
||||||
|
Output output = new Output();
|
||||||
|
output.setName("fahrenheitTemp");
|
||||||
|
output.setType(OutputType.TIME_SERIES);
|
||||||
|
output.setDecimalsByDefault(2);
|
||||||
|
config.setOutput(output);
|
||||||
|
|
||||||
|
calculatedField.setConfiguration(config);
|
||||||
|
|
||||||
|
return calculatedField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UplinkMsg getUplinkMsg(UUID uuid, CalculatedField calculatedField, UpdateMsgType updateMsgType) throws InvalidProtocolBufferException {
|
||||||
|
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
|
||||||
|
CalculatedFieldUpdateMsg.Builder calculatedFieldUpdateMsgBuilder = CalculatedFieldUpdateMsg.newBuilder();
|
||||||
|
calculatedFieldUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
|
||||||
|
calculatedFieldUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
|
||||||
|
calculatedFieldUpdateMsgBuilder.setEntity(JacksonUtil.toString(calculatedField));
|
||||||
|
calculatedFieldUpdateMsgBuilder.setMsgType(updateMsgType);
|
||||||
|
testAutoGeneratedCodeByProtobuf(calculatedFieldUpdateMsgBuilder);
|
||||||
|
uplinkMsgBuilder.addCalculatedFieldUpdateMsg(calculatedFieldUpdateMsgBuilder.build());
|
||||||
|
|
||||||
|
testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder);
|
||||||
|
|
||||||
|
return uplinkMsgBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCalculatedFieldOnCloud(UplinkMsg uplinkMsg, UUID uuid, String resourceTitle) throws Exception {
|
||||||
|
edgeImitator.expectResponsesAmount(1);
|
||||||
|
edgeImitator.sendUplinkMsg(uplinkMsg);
|
||||||
|
|
||||||
|
Assert.assertTrue(edgeImitator.waitForResponses());
|
||||||
|
|
||||||
|
UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
|
||||||
|
Assert.assertTrue(latestResponseMsg.getSuccess());
|
||||||
|
|
||||||
|
CalculatedField calculatedField = doGet("/api/calculatedField/" + uuid, CalculatedField.class);
|
||||||
|
Assert.assertNotNull(calculatedField);
|
||||||
|
Assert.assertEquals(resourceTitle, calculatedField.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -33,6 +33,7 @@ import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
|
|||||||
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
|
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
|
||||||
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
|
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
|
||||||
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
|
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
|
||||||
|
import org.thingsboard.server.gen.edge.v1.CalculatedFieldUpdateMsg;
|
||||||
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
|
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
|
||||||
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
|
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
|
||||||
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg;
|
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg;
|
||||||
@ -352,6 +353,11 @@ public class EdgeImitator {
|
|||||||
result.add(saveDownlinkMsg(notificationTargetUpdateMsg));
|
result.add(saveDownlinkMsg(notificationTargetUpdateMsg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (downlinkMsg.getCalculatedFieldUpdateMsgCount() > 0) {
|
||||||
|
for (CalculatedFieldUpdateMsg calculatedFieldUpdateMsg : downlinkMsg.getCalculatedFieldUpdateMsgList()) {
|
||||||
|
result.add(saveDownlinkMsg(calculatedFieldUpdateMsg));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (downlinkMsg.hasEdgeConfiguration()) {
|
if (downlinkMsg.hasEdgeConfiguration()) {
|
||||||
result.add(saveDownlinkMsg(downlinkMsg.getEdgeConfiguration()));
|
result.add(saveDownlinkMsg(downlinkMsg.getEdgeConfiguration()));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,18 +57,23 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CalculatedField save(CalculatedField calculatedField) {
|
public CalculatedField save(CalculatedField calculatedField) {
|
||||||
return doSave(calculatedField, true);
|
CalculatedField oldCalculatedField = calculatedFieldDataValidator.validate(calculatedField, CalculatedField::getTenantId);
|
||||||
|
|
||||||
|
return doSave(calculatedField, oldCalculatedField);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CalculatedField save(CalculatedField calculatedField, boolean doValidate) {
|
public CalculatedField save(CalculatedField calculatedField, boolean doValidate) {
|
||||||
return doSave(calculatedField, doValidate);
|
CalculatedField oldCalculatedField = null;
|
||||||
|
|
||||||
|
if (doValidate) {
|
||||||
|
oldCalculatedField = calculatedFieldDataValidator.validate(calculatedField, CalculatedField::getTenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return doSave(calculatedField, oldCalculatedField);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CalculatedField doSave(CalculatedField calculatedField, boolean doValidate) {
|
private CalculatedField doSave(CalculatedField calculatedField, CalculatedField oldCalculatedField) {
|
||||||
if (doValidate) {
|
|
||||||
calculatedFieldDataValidator.validate(calculatedField, CalculatedField::getTenantId);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
TenantId tenantId = calculatedField.getTenantId();
|
TenantId tenantId = calculatedField.getTenantId();
|
||||||
log.trace("Executing save calculated field, [{}]", calculatedField);
|
log.trace("Executing save calculated field, [{}]", calculatedField);
|
||||||
@ -76,7 +81,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
|
|||||||
CalculatedField savedCalculatedField = calculatedFieldDao.save(tenantId, calculatedField);
|
CalculatedField savedCalculatedField = calculatedFieldDao.save(tenantId, calculatedField);
|
||||||
createOrUpdateCalculatedFieldLink(tenantId, savedCalculatedField);
|
createOrUpdateCalculatedFieldLink(tenantId, savedCalculatedField);
|
||||||
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedCalculatedField.getTenantId()).entityId(savedCalculatedField.getId())
|
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedCalculatedField.getTenantId()).entityId(savedCalculatedField.getId())
|
||||||
.entity(savedCalculatedField).oldEntity(calculatedField).created(calculatedField.getId() == null).build());
|
.entity(savedCalculatedField).oldEntity(oldCalculatedField).created(calculatedField.getId() == null).build());
|
||||||
return savedCalculatedField;
|
return savedCalculatedField;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
checkConstraintViolation(e,
|
checkConstraintViolation(e,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user