Option to ignore SNMP response type cast errors
This commit is contained in:
parent
09b866c41f
commit
854e059435
@ -22,6 +22,7 @@ import com.google.gson.JsonPrimitive;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
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.security.permission.Resource;
|
||||||
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
|
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
|
||||||
import org.thingsboard.server.utils.CsvUtils;
|
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.Nullable;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
@ -269,7 +270,7 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
|
|||||||
if (!entry.getKey().getType().isKv()) {
|
if (!entry.getKey().getType().isKv()) {
|
||||||
entityData.getFields().put(entry.getKey().getType(), entry.getValue());
|
entityData.getFields().put(entry.getKey().getType(), entry.getValue());
|
||||||
} else {
|
} 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()));
|
entityData.getKvs().put(entry.getKey(), new ParsedValue(castResult.getValue(), castResult.getKey()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -975,6 +975,8 @@ transport:
|
|||||||
# to configure SNMP to work over UDP or TCP
|
# to configure SNMP to work over UDP or TCP
|
||||||
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
|
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
|
||||||
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
|
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
|
||||||
|
response:
|
||||||
|
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
|
||||||
stats:
|
stats:
|
||||||
enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"
|
enabled: "${TB_TRANSPORT_STATS_ENABLED:true}"
|
||||||
print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}"
|
print-interval-ms: "${TB_TRANSPORT_STATS_PRINT_INTERVAL_MS:60000}"
|
||||||
|
|||||||
@ -163,6 +163,15 @@ public class StringUtils {
|
|||||||
return false;
|
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) {
|
public static String substringAfterLast(String str, String sep) {
|
||||||
return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
|
return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,35 +13,53 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.math.NumberUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.thingsboard.server.common.data.kv.DataType;
|
import org.thingsboard.server.common.data.kv.DataType;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class TypeCastUtil {
|
public class TypeCastUtil {
|
||||||
|
|
||||||
private TypeCastUtil() {}
|
private TypeCastUtil() {}
|
||||||
|
|
||||||
public static Map.Entry<DataType, Object> castValue(String value) {
|
public static Pair<DataType, Object> castValue(String value) {
|
||||||
if (isNumber(value)) {
|
if (isNumber(value)) {
|
||||||
String formattedValue = value.replace(',', '.');
|
String formattedValue = value.replace(',', '.');
|
||||||
try {
|
try {
|
||||||
BigDecimal bd = new BigDecimal(formattedValue);
|
BigDecimal bd = new BigDecimal(formattedValue);
|
||||||
if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
|
if (bd.stripTrailingZeros().scale() > 0 || isSimpleDouble(formattedValue)) {
|
||||||
if (bd.scale() <= 16) {
|
if (bd.scale() <= 16) {
|
||||||
return Map.entry(DataType.DOUBLE, bd.doubleValue());
|
return Pair.of(DataType.DOUBLE, bd.doubleValue());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Map.entry(DataType.LONG, bd.longValueExact());
|
return Pair.of(DataType.LONG, bd.longValueExact());
|
||||||
}
|
}
|
||||||
} catch (RuntimeException ignored) {}
|
} catch (RuntimeException ignored) {}
|
||||||
} else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
|
} 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) {
|
private static boolean isNumber(String value) {
|
||||||
@ -17,6 +17,7 @@ package org.thingsboard.server.transport.snmp.service;
|
|||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.snmp4j.PDU;
|
import org.snmp4j.PDU;
|
||||||
import org.snmp4j.ScopedPDU;
|
import org.snmp4j.ScopedPDU;
|
||||||
@ -28,12 +29,14 @@ import org.snmp4j.smi.Variable;
|
|||||||
import org.snmp4j.smi.VariableBinding;
|
import org.snmp4j.smi.VariableBinding;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
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.device.data.SnmpDeviceTransportConfiguration;
|
||||||
import org.thingsboard.server.common.data.kv.DataType;
|
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.SnmpMapping;
|
||||||
import org.thingsboard.server.common.data.transport.snmp.SnmpMethod;
|
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.SnmpProtocolVersion;
|
||||||
import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig;
|
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.queue.util.TbSnmpTransportComponent;
|
||||||
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
|
import org.thingsboard.server.transport.snmp.session.DeviceSessionContext;
|
||||||
|
|
||||||
@ -48,11 +51,15 @@ import java.util.stream.Collectors;
|
|||||||
@TbSnmpTransportComponent
|
@TbSnmpTransportComponent
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class PduService {
|
public class PduService {
|
||||||
|
|
||||||
@Value("${transport.snmp.max_request_oids:100}")
|
@Value("${transport.snmp.max_request_oids:100}")
|
||||||
private int maxRequestOids;
|
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) {
|
public List<PDU> createPdus(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map<String, String> values) {
|
||||||
List<PDU> pdus = new ArrayList<>();
|
List<PDU> pdus = new ArrayList<>();
|
||||||
List<SnmpMapping> allMappings = communicationConfig.getAllMappings();
|
List<SnmpMapping> allMappings = communicationConfig.getAllMappings();
|
||||||
@ -165,20 +172,31 @@ public class PduService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void processValue(String key, DataType dataType, String value, JsonObject result) {
|
public void processValue(String key, DataType dataType, String value, JsonObject result) {
|
||||||
switch (dataType) {
|
try {
|
||||||
case LONG:
|
switch (dataType) {
|
||||||
result.addProperty(key, Long.parseLong(value));
|
case STRING:
|
||||||
break;
|
case JSON:
|
||||||
case BOOLEAN:
|
result.addProperty(key, value);
|
||||||
result.addProperty(key, Boolean.parseBoolean(value));
|
break;
|
||||||
break;
|
case LONG:
|
||||||
case DOUBLE:
|
case DOUBLE:
|
||||||
result.addProperty(key, Double.parseDouble(value));
|
result.addProperty(key, TypeCastUtil.castToNumber(value).getValue());
|
||||||
break;
|
break;
|
||||||
case STRING:
|
case BOOLEAN:
|
||||||
case JSON:
|
if (StringUtils.equalsAnyIgnoreCase(value, "true", "false")) {
|
||||||
default:
|
result.addProperty(key, Boolean.parseBoolean(value));
|
||||||
result.addProperty(key, 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,6 +104,8 @@ transport:
|
|||||||
# to configure SNMP to work over UDP or TCP
|
# to configure SNMP to work over UDP or TCP
|
||||||
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
|
underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}"
|
||||||
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
|
max_request_oids: "${SNMP_MAX_REQUEST_OIDS:100}"
|
||||||
|
response:
|
||||||
|
ignore_type_cast_errors: "${SNMP_RESPONSE_IGNORE_TYPE_CAST_ERRORS:false}"
|
||||||
sessions:
|
sessions:
|
||||||
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
|
inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
|
||||||
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
|
report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user