Option to ignore SNMP response type cast errors

This commit is contained in:
ViacheslavKlimov 2023-06-22 18:19:16 +03:00
parent 09b866c41f
commit 854e059435
6 changed files with 73 additions and 23 deletions

View File

@ -22,6 +22,7 @@ import com.google.gson.JsonPrimitive;
import lombok.Data;
import lombok.SneakyThrows;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@ -57,7 +58,7 @@ import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.utils.CsvUtils;
import org.thingsboard.server.utils.TypeCastUtil;
import org.thingsboard.server.common.data.util.TypeCastUtil;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
@ -269,7 +270,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
if (!entry.getKey().getType().isKv()) {
entityData.getFields().put(entry.getKey().getType(), entry.getValue());
} else {
Map.Entry<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue());
Pair<DataType, Object> castResult = TypeCastUtil.castValue(entry.getValue());
entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey()));
}
});

View File

@ -975,6 +975,8 @@ transport:
# to configure SNMP to work over UDP or TCP
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
response:
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
stats:
enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"
print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}"

View File

@ -163,6 +163,15 @@ public class StringUtils {
return false;
}
public static boolean equalsAnyIgnoreCase(String string, String... otherStrings) {
for (String otherString : otherStrings) {
if (equalsIgnoreCase(string, otherString)) {
return true;
}
}
return false;
}
public static String substringAfterLast(String str, String sep) {
return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
}

View File

@ -13,35 +13,53 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.utils;
package org.thingsboard.server.common.data.util;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.thingsboard.server.common.data.kv.DataType;
import java.math.BigDecimal;
import java.util.Map;
public class TypeCastUtil {
private TypeCastUtil() {}
public static Map.Entry<DataType, Object> castValue(String value) {
public static Pair<DataType, Object> castValue(String value) {
if (isNumber(value)) {
String formattedValue = value.replace(',', '.');
try {
BigDecimal bd = new BigDecimal(formattedValue);
if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
if (bd.scale() <= 16) {
return Map.entry(DataType.DOUBLE, bd.doubleValue());
return Pair.of(DataType.DOUBLE, bd.doubleValue());
}
} else {
return Map.entry(DataType.LONG, bd.longValueExact());
return Pair.of(DataType.LONG, bd.longValueExact());
}
} catch (RuntimeException ignored) {}
} else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
return Map.entry(DataType.BOOLEAN, Boolean.parseBoolean(value));
return Pair.of(DataType.BOOLEAN, Boolean.parseBoolean(value));
}
return Pair.of(DataType.STRING, value);
}
public static Pair<DataType, Number> castToNumber(String value) {
if (isNumber(value)) {
String formattedValue = value.replace(',', '.');
BigDecimal bd = new BigDecimal(formattedValue);
if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
if (bd.scale() <= 16) {
return Pair.of(DataType.DOUBLE, bd.doubleValue());
} else {
return Pair.of(DataType.DOUBLE, bd);
}
} else {
return Pair.of(DataType.LONG, bd.longValueExact());
}
} else {
throw new IllegalArgumentException("'" + value + "' can't be parsed as number");
}
return Map.entry(DataType.STRING, value);
}
private static boolean isNumber(String value) {

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.snmp.service;
import com.google.common.collect.Lists;
import com.google.gson.JsonObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.PDU;
import org.snmp4j.ScopedPDU;
@ -28,12 +29,14 @@ import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.transport.snmp.SnmpMapping;
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion;
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
import org.thingsboard.server.common.data.util.TypeCastUtil;
import org.thingsboard.server.queue.util.TbSnmpTransportComponent;
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
@ -48,11 +51,15 @@ import java.util.stream.Collectors;
@TbSnmpTransportComponent
@Service
@Slf4j
@RequiredArgsConstructor
public class PduService {
@Value("${transport.snmp.max_request_oids:100}")
private int maxRequestOids;
@Value("${transport.snmp.response.ignore_type_cast_errors:false}")
private boolean ignoreTypeCastErrors;
public List<PDU> createPdus(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
List<PDU> pdus = new ArrayList<>();
List<SnmpMapping> allMappings = communicationConfig.getAllMappings();
@ -165,20 +172,31 @@ public class PduService {
}
public void processValue(String key, DataType dataType, String value, JsonObject result) {
switch (dataType) {
case LONG:
result.addProperty(key, Long.parseLong(value));
break;
case BOOLEAN:
result.addProperty(key, Boolean.parseBoolean(value));
break;
case DOUBLE:
result.addProperty(key, Double.parseDouble(value));
break;
case STRING:
case JSON:
default:
result.addProperty(key, value);
try {
switch (dataType) {
case STRING:
case JSON:
result.addProperty(key, value);
break;
case LONG:
case DOUBLE:
result.addProperty(key, TypeCastUtil.castToNumber(value).getValue());
break;
case BOOLEAN:
if (StringUtils.equalsAnyIgnoreCase(value, "true", "false")) {
result.addProperty(key, Boolean.parseBoolean(value));
} else {
throw new IllegalArgumentException("Can't parse '" + value + "' as boolean");
}
break;
}
} catch (IllegalArgumentException e) {
if (ignoreTypeCastErrors) {
log.debug("Ignoring value '{}' for key '{}' because of data type mismatch ({} required)", value, key, dataType);
} else {
throw e;
}
}
}
}

View File

@ -104,6 +104,8 @@ transport:
# to configure SNMP to work over UDP or TCP
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
response:
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
sessions:
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"