Merge branch 'master' into feature/jdk11
This commit is contained in:
commit
2bd28cb021
@ -203,6 +203,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
} catch (Exception t) {
|
} catch (Exception t) {
|
||||||
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
|
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
|
||||||
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) {
|
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) {
|
||||||
|
// remove device from cache in case null value cached in the distributed redis.
|
||||||
|
removeDeviceFromCache(device.getTenantId(), device.getName());
|
||||||
throw new DataValidationException("Device with such name already exists!");
|
throw new DataValidationException("Device with such name already exists!");
|
||||||
} else {
|
} else {
|
||||||
throw t;
|
throw t;
|
||||||
@ -281,15 +283,19 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
|
|||||||
}
|
}
|
||||||
deleteEntityRelations(tenantId, deviceId);
|
deleteEntityRelations(tenantId, deviceId);
|
||||||
|
|
||||||
List<Object> list = new ArrayList<>();
|
removeDeviceFromCache(tenantId, device.getName());
|
||||||
list.add(device.getTenantId());
|
|
||||||
list.add(device.getName());
|
|
||||||
Cache cache = cacheManager.getCache(DEVICE_CACHE);
|
|
||||||
cache.evict(list);
|
|
||||||
|
|
||||||
deviceDao.removeById(tenantId, deviceId.getId());
|
deviceDao.removeById(tenantId, deviceId.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeDeviceFromCache(TenantId tenantId, String name) {
|
||||||
|
List<Object> list = new ArrayList<>();
|
||||||
|
list.add(tenantId);
|
||||||
|
list.add(name);
|
||||||
|
Cache cache = cacheManager.getCache(DEVICE_CACHE);
|
||||||
|
cache.evict(list);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageData<Device> findDevicesByTenantId(TenantId tenantId, PageLink pageLink) {
|
public PageData<Device> findDevicesByTenantId(TenantId tenantId, PageLink pageLink) {
|
||||||
log.trace("Executing findDevicesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
|
log.trace("Executing findDevicesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
|
||||||
|
|||||||
@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2021 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.rule.engine.metadata;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.thingsboard.common.util.DonAsynchron;
|
||||||
|
import org.thingsboard.rule.engine.api.RuleNode;
|
||||||
|
import org.thingsboard.rule.engine.api.TbContext;
|
||||||
|
import org.thingsboard.rule.engine.api.TbNode;
|
||||||
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
||||||
|
import org.thingsboard.rule.engine.api.TbNodeException;
|
||||||
|
import org.thingsboard.rule.engine.api.TbRelationTypes;
|
||||||
|
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
||||||
|
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
|
||||||
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
|
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
||||||
|
import org.thingsboard.server.common.data.plugin.ComponentType;
|
||||||
|
import org.thingsboard.server.common.msg.TbMsg;
|
||||||
|
import org.thingsboard.server.dao.timeseries.TimeseriesService;
|
||||||
|
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RuleNode(type = ComponentType.ENRICHMENT,
|
||||||
|
name = "calculate delta",
|
||||||
|
configClazz = CalculateDeltaNodeConfiguration.class,
|
||||||
|
nodeDescription = "Calculates and adds 'delta' value into message based on the incoming and previous value",
|
||||||
|
nodeDetails = "Calculates delta and period based on the previous time-series reading and current data. " +
|
||||||
|
"Delta calculation is done in scope of the message originator, e.g. device, asset or customer.",
|
||||||
|
uiResources = {"static/rulenode/rulenode-core-config.js"},
|
||||||
|
configDirective = "tbEnrichmentNodeCalculateDeltaConfig")
|
||||||
|
public class CalculateDeltaNode implements TbNode {
|
||||||
|
private Map<EntityId, ValueWithTs> cache;
|
||||||
|
private CalculateDeltaNodeConfiguration config;
|
||||||
|
private TbContext ctx;
|
||||||
|
private TimeseriesService timeseriesService;
|
||||||
|
private boolean useCache;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
|
||||||
|
this.config = TbNodeUtils.convert(configuration, CalculateDeltaNodeConfiguration.class);
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.timeseriesService = ctx.getTimeseriesService();
|
||||||
|
this.useCache = config.isUseCache();
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
cache = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMsg(TbContext ctx, TbMsg msg) {
|
||||||
|
JsonNode json = JacksonUtil.toJsonNode(msg.getData());
|
||||||
|
String inputKey = config.getInputValueKey();
|
||||||
|
if (json.has(inputKey)) {
|
||||||
|
DonAsynchron.withCallback(getLastValue(msg.getOriginator()),
|
||||||
|
previousData -> {
|
||||||
|
double currentValue = json.get(inputKey).asDouble();
|
||||||
|
long currentTs = TbMsgTimeseriesNode.getTs(msg);
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal delta = BigDecimal.valueOf(previousData != null ? currentValue - previousData.value : 0.0);
|
||||||
|
|
||||||
|
if (config.isTellFailureIfDeltaIsNegative() && delta.doubleValue() < 0) {
|
||||||
|
ctx.tellNext(msg, TbRelationTypes.FAILURE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.getRound() != null) {
|
||||||
|
delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectNode result = (ObjectNode) json;
|
||||||
|
result.put(config.getOutputValueKey(), delta);
|
||||||
|
|
||||||
|
if (config.isAddPeriodBetweenMsgs()) {
|
||||||
|
long period = previousData != null ? currentTs - previousData.ts : 0;
|
||||||
|
result.put(config.getPeriodValueKey(), period);
|
||||||
|
}
|
||||||
|
ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result)));
|
||||||
|
},
|
||||||
|
t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
|
||||||
|
} else if (config.isTellFailureIfInputValueKeyIsAbsent()) {
|
||||||
|
ctx.tellNext(msg, TbRelationTypes.FAILURE);
|
||||||
|
} else {
|
||||||
|
ctx.tellSuccess(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
if (useCache) {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<ValueWithTs> fetchLatestValue(EntityId entityId) {
|
||||||
|
return Futures.transform(timeseriesService.findLatest(ctx.getTenantId(), entityId, Collections.singletonList(config.getInputValueKey())),
|
||||||
|
list -> extractValue(list.get(0))
|
||||||
|
, ctx.getDbCallbackExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<ValueWithTs> getLastValue(EntityId entityId) {
|
||||||
|
ValueWithTs latestValue;
|
||||||
|
if (useCache && (latestValue = cache.get(entityId)) != null) {
|
||||||
|
return Futures.immediateFuture(latestValue);
|
||||||
|
} else {
|
||||||
|
return fetchLatestValue(entityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueWithTs extractValue(TsKvEntry kvEntry) {
|
||||||
|
if (kvEntry == null || kvEntry.getValue() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
double result = 0.0;
|
||||||
|
long ts = kvEntry.getTs();
|
||||||
|
switch (kvEntry.getDataType()) {
|
||||||
|
case LONG:
|
||||||
|
result = kvEntry.getLongValue().get();
|
||||||
|
break;
|
||||||
|
case DOUBLE:
|
||||||
|
result = kvEntry.getDoubleValue().get();
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
try {
|
||||||
|
result = Double.parseDouble(kvEntry.getStrValue().get());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Calculation failed. Unable to parse value [" + kvEntry.getStrValue().get() + "]" +
|
||||||
|
" of telemetry [" + kvEntry.getKey() + "] to Double");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BOOLEAN:
|
||||||
|
throw new IllegalArgumentException("Calculation failed. Boolean values are not supported!");
|
||||||
|
case JSON:
|
||||||
|
throw new IllegalArgumentException("Calculation failed. JSON values are not supported!");
|
||||||
|
}
|
||||||
|
return new ValueWithTs(ts, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ValueWithTs {
|
||||||
|
private final long ts;
|
||||||
|
private final double value;
|
||||||
|
|
||||||
|
private ValueWithTs(long ts, double value) {
|
||||||
|
this.ts = ts;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2021 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.rule.engine.metadata;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.thingsboard.rule.engine.api.NodeConfiguration;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CalculateDeltaNodeConfiguration implements NodeConfiguration<CalculateDeltaNodeConfiguration> {
|
||||||
|
private String inputValueKey;
|
||||||
|
private String outputValueKey;
|
||||||
|
private boolean useCache;
|
||||||
|
private boolean addPeriodBetweenMsgs;
|
||||||
|
private String periodValueKey;
|
||||||
|
private Integer round;
|
||||||
|
private boolean tellFailureIfInputValueKeyIsAbsent;
|
||||||
|
private boolean tellFailureIfDeltaIsNegative;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CalculateDeltaNodeConfiguration defaultConfiguration() {
|
||||||
|
CalculateDeltaNodeConfiguration configuration = new CalculateDeltaNodeConfiguration();
|
||||||
|
configuration.setInputValueKey("pulseCounter");
|
||||||
|
configuration.setOutputValueKey("delta");
|
||||||
|
configuration.setUseCache(true);
|
||||||
|
configuration.setAddPeriodBetweenMsgs(false);
|
||||||
|
configuration.setPeriodValueKey("periodInMs");
|
||||||
|
configuration.setTellFailureIfInputValueKeyIsAbsent(true);
|
||||||
|
configuration.setTellFailureIfDeltaIsNegative(true);
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -223,7 +223,11 @@ class AlarmState {
|
|||||||
currentAlarm.setType(alarmDefinition.getAlarmType());
|
currentAlarm.setType(alarmDefinition.getAlarmType());
|
||||||
currentAlarm.setStatus(AlarmStatus.ACTIVE_UNACK);
|
currentAlarm.setStatus(AlarmStatus.ACTIVE_UNACK);
|
||||||
currentAlarm.setSeverity(severity);
|
currentAlarm.setSeverity(severity);
|
||||||
currentAlarm.setStartTs(System.currentTimeMillis());
|
long startTs = dataSnapshot.getTs();
|
||||||
|
if (startTs == 0L) {
|
||||||
|
startTs = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
currentAlarm.setStartTs(startTs);
|
||||||
currentAlarm.setEndTs(currentAlarm.getStartTs());
|
currentAlarm.setEndTs(currentAlarm.getStartTs());
|
||||||
currentAlarm.setDetails(createDetails(ruleState));
|
currentAlarm.setDetails(createDetails(ruleState));
|
||||||
currentAlarm.setOriginator(originator);
|
currentAlarm.setOriginator(originator);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user