Commit 248f21cb35807cb1cdb5143f3c9dae457a30e96f

Authored by xp.Huang
1 parent e4889489

feat: 增加标准modbus解析

Showing 25 changed files with 1452 additions and 118 deletions
@@ -22,19 +22,18 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -22,19 +22,18 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
22 import org.thingsboard.server.common.data.yunteng.common.AddGroup; 22 import org.thingsboard.server.common.data.yunteng.common.AddGroup;
23 import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; 23 import org.thingsboard.server.common.data.yunteng.common.DeleteGroup;
24 import org.thingsboard.server.common.data.yunteng.common.UpdateGroup; 24 import org.thingsboard.server.common.data.yunteng.common.UpdateGroup;
  25 +import org.thingsboard.server.common.data.yunteng.core.exception.ThingsKitException;
25 import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; 26 import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
26 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; 27 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
27 import org.thingsboard.server.common.data.yunteng.dto.*; 28 import org.thingsboard.server.common.data.yunteng.dto.*;
28 import org.thingsboard.server.common.data.yunteng.dto.thingsmodel.ImportThingsModelDTO; 29 import org.thingsboard.server.common.data.yunteng.dto.thingsmodel.ImportThingsModelDTO;
29 -import org.thingsboard.server.common.data.yunteng.enums.FunctionTypeEnum;  
30 -import org.thingsboard.server.common.data.yunteng.enums.ImportEnum;  
31 -import org.thingsboard.server.common.data.yunteng.enums.OrderTypeEnum;  
32 -import org.thingsboard.server.common.data.yunteng.enums.StatusEnum; 30 +import org.thingsboard.server.common.data.yunteng.enums.*;
33 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult; 31 import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult;
34 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; 32 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
35 import org.thingsboard.server.controller.BaseController; 33 import org.thingsboard.server.controller.BaseController;
36 import org.thingsboard.server.dao.yunteng.service.ThingsModelService; 34 import org.thingsboard.server.dao.yunteng.service.ThingsModelService;
37 import org.thingsboard.server.dao.yunteng.service.TkDeviceProfileService; 35 import org.thingsboard.server.dao.yunteng.service.TkDeviceProfileService;
  36 +import org.thingsboard.server.utils.ImportModbusUtils;
38 37
39 import java.io.File; 38 import java.io.File;
40 import java.io.IOException; 39 import java.io.IOException;
@@ -42,6 +41,7 @@ import java.io.InputStream; @@ -42,6 +41,7 @@ import java.io.InputStream;
42 import java.util.HashMap; 41 import java.util.HashMap;
43 import java.util.List; 42 import java.util.List;
44 import java.util.Map; 43 import java.util.Map;
  44 +import java.util.regex.Pattern;
45 45
46 import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*; 46 import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*;
47 import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.ORDER_TYPE; 47 import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.ORDER_TYPE;
@@ -234,16 +234,221 @@ public class ThingsModelController extends BaseController { @@ -234,16 +234,221 @@ public class ThingsModelController extends BaseController {
234 return thingsModelService.saveOrUpdate(thingsModelDTO); 234 return thingsModelService.saveOrUpdate(thingsModelDTO);
235 } 235 }
236 236
  237 + @GetMapping("/download/template")
  238 + @ApiOperation("excel模板下载")
  239 + public ResponseEntity<Resource> downloadTemplate(String type) throws IOException {
  240 + String name = "template"+ File.separator;
  241 + if(StringUtils.isNotEmpty(type)&&type.equals("modbus")){
  242 + name = name +"modbusTemplates.xls";
  243 + }else{
  244 + name = name+"template.xls";
  245 + }
  246 + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(name);
  247 + if(inputStream!=null){
  248 + HttpHeaders headers = new HttpHeaders();
  249 + headers.add(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=物模型属性导入模板.xls");
  250 + return ResponseEntity.ok()
  251 + .headers(headers)
  252 + .contentType(MediaType.APPLICATION_OCTET_STREAM)
  253 + .body(new InputStreamResource(inputStream));
  254 + }else {
  255 + return null;
  256 + }
  257 + }
  258 +
  259 +
  260 + @PostMapping("/batch/get_tsl")
  261 + @ApiOperation("获取多个产品的物模型数据")
  262 + public ResponseEntity<List<BatchDeviceProfileInfoDTO>> getDeviceProfilesTSL(@RequestBody DeviceProfileTSLDTO profileTSLDTO)
  263 + throws ThingsboardException {
  264 + if(null == profileTSLDTO.getDeviceProfileIds() || null == profileTSLDTO.getFunctionTypeEnum() ||
  265 + profileTSLDTO.getDeviceProfileIds().isEmpty()){
  266 + throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  267 + }
  268 + return ResponseEntity.ok(thingsModelService.getDeviceProfileTSLInfoByIds(getCurrentUser().getCurrentTenantId(),
  269 + profileTSLDTO.getDeviceProfileIds(),profileTSLDTO.getFunctionTypeEnum()));
  270 + }
  271 +
237 @ApiOperation(value = "通过excel导入物模型") 272 @ApiOperation(value = "通过excel导入物模型")
238 @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN'},{'api:yt:things_model:excel_import'})") 273 @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN'},{'api:yt:things_model:excel_import'})")
239 @PostMapping("/csvImport") 274 @PostMapping("/csvImport")
240 - public ResponseResult<String> csvImport(String deviceProfileId, String categoryId, 275 + public ResponseResult<String> csvImport(String deviceProfileId, String categoryId,String type,
241 @RequestPart("file") MultipartFile file) throws Exception { 276 @RequestPart("file") MultipartFile file) throws Exception {
242 if(file.isEmpty()){ 277 if(file.isEmpty()){
243 - return null; 278 + throw new ThingsKitException(ErrorMessage.INVALID_PARAMETER);
244 } 279 }
  280 + try{
245 Workbook work = WorkbookFactory.create(file.getInputStream()); 281 Workbook work = WorkbookFactory.create(file.getInputStream());
  282 + String tenantId = getCurrentUser().getCurrentTenantId();
  283 + if(type.equals("modbus")){
  284 + return importModbus(deviceProfileId,categoryId,file,tenantId,work);
  285 + }else{
  286 + return importModel(deviceProfileId,categoryId,file,tenantId,work);
  287 + }
  288 + }catch (Exception e){
  289 + throw new TkDataValidationException(ErrorMessage.IMPORT_ERROR.getMessage());
  290 + }
  291 + }
  292 +
  293 +
  294 + ResponseResult<String> importModbus(String deviceProfileId, String categoryId,MultipartFile file,String tenantId, Workbook work) throws IOException {
  295 +
  296 + Sheet sheet = work.getSheetAt(0);
  297 + int succeed = 0;//成功数量
  298 + int failed =0;//失败数量
  299 + StringBuffer failedString = new StringBuffer();
  300 + String excel = sheet.getRow(0).getCell(2).toString();
  301 + if(!excel.equals("MODBUS_RTU操作类型*")){
  302 + throw new TkDataValidationException(ErrorMessage.IMPORT_ERROR.getMessage());
  303 + }
  304 + for(int i = 1; i<=sheet.getPhysicalNumberOfRows(); i++) {
  305 + Row row = sheet.getRow(i);
  306 + if(row!=null){
  307 + String functionName = row.getCell(0).toString();//功能名称
  308 + if(StringUtils.isEmpty(functionName)){
  309 + failed++;
  310 + failedString.append("第"+i+"条,功能名称未填;");
  311 + continue;
  312 + }
  313 + String identifier = row.getCell(1).toString();//标识符*
  314 + if(StringUtils.isEmpty(identifier)){
  315 + failed++;
  316 + failedString.append("第"+i+"条,标识符未填;");
  317 + continue;
  318 + }
  319 + Boolean identifierState = thingsModelService.getByIdentifier(tenantId,deviceProfileId,categoryId,identifier);
  320 + if(!identifierState){
  321 + failed++;
  322 + failedString.append("第"+i+"条,标识符已存在;");
  323 + continue;
  324 + }
  325 + ThingsModelDTO dto = new ThingsModelDTO();
  326 +
  327 + String extensionJson ="";
  328 + String json ="";
  329 +
  330 + if(identifier.equals("source")){
  331 + dto.setAccessMode("r");
  332 + extensionJson ="{\"writeOnly\":true}";
  333 + json ="{\"dataType\":{\"type\":\"STRING\",\"specs\":{}}}";
  334 + }else{
  335 + String operationType = row.getCell(2).toString();//MODBUS_RTU操作类型*
  336 + if(StringUtils.isEmpty(operationType)){
  337 + failed++;
  338 + failedString.append("第"+i+"条,MODBUS_RTU操作类型未填;");
  339 + continue;
  340 + }
  341 + String originalDataType = row.getCell(3).toString();//MODBUS_RTU数据类型*
  342 + if(StringUtils.isEmpty(originalDataType)){
  343 + failed++;
  344 + failedString.append("第"+i+"条,MODBUS_RTU数据类型未填;");
  345 + continue;
  346 + }
  347 + operationType =OperationTypeEnum.getName(operationType);
  348 + originalDataType = AttributeSourceDataTypeEnum.getName(originalDataType);
  349 + if(!ImportModbusUtils.operationTypeAndOriginalDataTypeStatus(operationType, originalDataType)){
  350 + failed++;
  351 + failedString.append("第"+i+"条,操作类型与数据类型不匹配;");
  352 + continue;
  353 + }
  354 +
  355 +
  356 + String registerType = row.getCell(5).toString();//寄存器类型
  357 + String registerAddress =row.getCell(6).toString();//寄存器地址
  358 + if(StringUtils.isEmpty(registerType)){
  359 + failed++;
  360 + failedString.append("第"+i+"条,寄存器类型未填;");
  361 + continue;
  362 + }
  363 + //转换校验输入的字符
  364 + if(registerType.equals("HEX")){
  365 + String hexPattern="^[0-9A-Fa-f]+$";
  366 + if(!Pattern.matches(hexPattern,registerAddress)){
  367 + failedString.append("第"+i+"条,寄存器地址格式不正确;");
  368 + continue;
  369 + }
  370 + }
  371 + if(registerType.equals("DEC")){
  372 + try{
  373 + registerAddress = Integer.toHexString(Integer.parseInt(registerAddress));
  374 + } catch (NumberFormatException e) {
  375 + failedString.append("第"+i+"条,寄存器地址格式不正确;");
  376 + continue;
  377 + }
  378 + }
  379 +
  380 + String bitMask = row.getCell(7)==null?null:row.getCell(7).toString();//比特位*
  381 + if(originalDataType.equals("位")&&StringUtils.isEmpty(bitMask)){
  382 + failed++;
  383 + failedString.append("第"+i+"条,比特位置(数据类型为位时必填);");
  384 + continue;
  385 + }
  386 + if(StringUtils.isEmpty(bitMask)){
  387 + bitMask="7";
  388 + }
  389 +
  390 + String scaling = row.getCell(8)==null?"1":row.getCell(8).toString();//缩放因子
  391 + String registerCount = row.getCell(9)==null?"":row.getCell(9).toString();//寄存器个数
  392 +
  393 + if(originalDataType.equals("字符串")&&StringUtils.isEmpty(registerCount)){
  394 + failed++;
  395 + failedString.append("第"+i+"条,寄存器个数(数据类型为字符串时必填);");
  396 + continue;
  397 + }
  398 +
  399 +
  400 + String unit ="";
  401 + String valueRange ="";
  402 + String dataType = ImportModbusUtils.getOriginalDataTypeIsDateType(originalDataType);
  403 + String cell4 = row.getCell(4)==null?null:row.getCell(4).toString();//单位
  404 + if(cell4!=null){
  405 + String[] dws = cell4.split("/");
  406 + unit = "\"unit\":\""+dws[1]+"\",\"unitName\":\""+cell4+"\"";
  407 + }
  408 + if(unit.equals("")){
  409 + valueRange = ImportModbusUtils.getOriginalDataTypeIsValueRange(originalDataType);
  410 + }else{
  411 + valueRange = ","+ImportModbusUtils.getOriginalDataTypeIsValueRange(originalDataType);
  412 + }
  413 + json ="{\"dataType\":{\"type\":\""+dataType+"\",\"specs\":{"+unit+valueRange+"}}}";
  414 + extensionJson =
  415 + "{\"writeOnly\":"+ImportModbusUtils.getOperationTypeIsWriteOnly(operationType)+
  416 + ",\"bitMask\":"+bitMask+",\"operationType\":\""+operationType+"\",\"originalDataType\":\""+originalDataType+
  417 + "\",\"registerAddress\":\""+registerAddress+"\",\"scaling\":"+scaling+"}";
  418 + if(!registerCount.equals("")){
  419 + extensionJson = extensionJson+",\"registerCount\":"+registerCount;
  420 + }
  421 + dto.setAccessMode(ImportModbusUtils.getOperationTypeIsRw(operationType));
  422 + }
  423 +
  424 + dto.setDeviceProfileId(deviceProfileId);
  425 + dto.setCategoryId(categoryId);
  426 + dto.setFunctionType(FunctionTypeEnum.properties);
  427 + dto.setFunctionName(functionName);
  428 + dto.setIdentifier(identifier);
  429 + JsonNode functionJson = JacksonUtil.toJsonNode(json);
  430 + JsonNode extensionDesc = JacksonUtil.toJsonNode(extensionJson);
  431 + dto.setFunctionJson(functionJson);
  432 + dto.setExtensionDesc(extensionDesc);
  433 + dto.setRemark(row.getCell(10)==null?null:row.getCell(10).toString());
  434 + dto.setTenantId(tenantId);
  435 + if(dto.getCategoryId()!=null){
  436 + thingsModelService.categorySaveOrUpdate(dto);
  437 + }else{
  438 + thingsModelService.saveOrUpdate(dto);
  439 + }
  440 + succeed++;
  441 + }
  442 + }
  443 + return ResponseResult.success("操作成功,导入"+succeed+"条;失败"+failed+"条;失败原因:"+failedString);
  444 + }
  445 +
  446 + ResponseResult<String> importModel(String deviceProfileId, String categoryId,MultipartFile file,String tenantId, Workbook work) throws IOException {
246 Sheet sheet = work.getSheetAt(0); 447 Sheet sheet = work.getSheetAt(0);
  448 + String excel = sheet.getRow(0).getCell(2).toString();
  449 + if(!excel.equals("数据类型*")){
  450 + return ResponseResult.failed(500,"操作失败,请使用下载模板excel重新导入");
  451 + }
247 int succeed = 0;//成功数量 452 int succeed = 0;//成功数量
248 int failed =0;//失败数量 453 int failed =0;//失败数量
249 StringBuffer failedString = new StringBuffer(); 454 StringBuffer failedString = new StringBuffer();
@@ -282,8 +487,6 @@ public class ThingsModelController extends BaseController { @@ -282,8 +487,6 @@ public class ThingsModelController extends BaseController {
282 continue; 487 continue;
283 } 488 }
284 } 489 }
285 -  
286 - String tenantId = getCurrentUser().getCurrentTenantId();  
287 Boolean identifierState = thingsModelService.getByIdentifier(tenantId,deviceProfileId,categoryId,identifier); 490 Boolean identifierState = thingsModelService.getByIdentifier(tenantId,deviceProfileId,categoryId,identifier);
288 if(!identifierState){ 491 if(!identifierState){
289 failed++; 492 failed++;
@@ -349,32 +552,4 @@ public class ThingsModelController extends BaseController { @@ -349,32 +552,4 @@ public class ThingsModelController extends BaseController {
349 } 552 }
350 return ResponseResult.success("操作成功,导入"+succeed+"条;失败"+failed+"条;失败原因:"+failedString.toString()); 553 return ResponseResult.success("操作成功,导入"+succeed+"条;失败"+failed+"条;失败原因:"+failedString.toString());
351 } 554 }
352 -  
353 - @GetMapping("/downloadTemplate")  
354 - @ApiOperation("excel模板下载")  
355 - public ResponseEntity<Resource> downloadTemplate() throws IOException {  
356 - InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template"+ File.separator +"template.xls");  
357 - if(inputStream!=null){  
358 - HttpHeaders headers = new HttpHeaders();  
359 - headers.add(HttpHeaders.CONTENT_DISPOSITION,"attachment;filename=物模型属性导入模板.xls");  
360 - return ResponseEntity.ok()  
361 - .headers(headers)  
362 - .contentType(MediaType.APPLICATION_OCTET_STREAM)  
363 - .body(new InputStreamResource(inputStream));  
364 - }else {  
365 - return null;  
366 - }  
367 - }  
368 -  
369 - @PostMapping("/batch/get_tsl")  
370 - @ApiOperation("获取多个产品的物模型数据")  
371 - public ResponseEntity<List<BatchDeviceProfileInfoDTO>> getDeviceProfilesTSL(@RequestBody DeviceProfileTSLDTO profileTSLDTO)  
372 - throws ThingsboardException {  
373 - if(null == profileTSLDTO.getDeviceProfileIds() || null == profileTSLDTO.getFunctionTypeEnum() ||  
374 - profileTSLDTO.getDeviceProfileIds().isEmpty()){  
375 - throw new TkDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());  
376 - }  
377 - return ResponseEntity.ok(thingsModelService.getDeviceProfileTSLInfoByIds(getCurrentUser().getCurrentTenantId(),  
378 - profileTSLDTO.getDeviceProfileIds(),profileTSLDTO.getFunctionTypeEnum()));  
379 - }  
380 } 555 }
  1 +package org.thingsboard.server.utils;
  2 +
  3 +import org.thingsboard.server.common.data.yunteng.enums.AttributeSourceDataTypeEnum;
  4 +import org.thingsboard.server.common.data.yunteng.enums.OperationTypeEnum;
  5 +
  6 +public class ImportModbusUtils {
  7 +
  8 + public static Boolean operationTypeAndOriginalDataTypeStatus(String operationType, String OriginalDataType){
  9 + if((operationType.equals(OperationTypeEnum.inputStatus_r_02.name())||
  10 + operationType.equals(OperationTypeEnum.coilStatus_r_01.name())||
  11 + operationType.equals(OperationTypeEnum.coilStatus_rw_01_05.name())||
  12 + operationType.equals(OperationTypeEnum.coilStatus_rw_01_0F.name())||
  13 + operationType.equals(OperationTypeEnum.coilStatus_w_05.name())||
  14 + operationType.equals(OperationTypeEnum.coilStatus_w_0F.name()))&&!OriginalDataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())){
  15 + return false;
  16 + }
  17 + if((operationType.equals(OperationTypeEnum.holdingRegister_rw_03_06.name())||
  18 + operationType.equals(OperationTypeEnum.holdingRegister_w_06.name()))&&
  19 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.INT16_AB.name())&&
  20 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.INT16_BA.name())&&
  21 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT16_AB.name())&&
  22 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT16_BA.name())&&
  23 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())&&
  24 + !OriginalDataType.equals(AttributeSourceDataTypeEnum.BITS.name())
  25 + ){
  26 + return false;
  27 + }
  28 + return true;
  29 + }
  30 +
  31 +
  32 + public static String getOriginalDataTypeIsDateType(String OriginalDataType){
  33 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.FLOAT_AB_CD.name())||
  34 + OriginalDataType.equals(AttributeSourceDataTypeEnum.FLOAT_CD_AB.name())||
  35 + OriginalDataType.equals(AttributeSourceDataTypeEnum.FLOAT_BA_DC.name())||
  36 + OriginalDataType.equals(AttributeSourceDataTypeEnum.FLOAT_DC_BA.name())||
  37 + OriginalDataType.equals(AttributeSourceDataTypeEnum.DOUBLE.name())){
  38 + return "DOUBLE";
  39 + }
  40 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())||
  41 + OriginalDataType.equals(AttributeSourceDataTypeEnum.BITS.name())){
  42 + return "BOOLEAN";
  43 + }
  44 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.STRING.name())){
  45 + return "STRING";
  46 + }
  47 + return "INT";
  48 + }
  49 +
  50 + public static String getOriginalDataTypeIsValueRange(String OriginalDataType){
  51 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.INT16_AB.name())||
  52 + OriginalDataType.equals(AttributeSourceDataTypeEnum.INT16_BA.name())){
  53 + return "\"valueRange\":{\"min\":-32768,\"max\":32767}";
  54 + }
  55 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT16_AB.name())||
  56 + OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT16_BA.name())){
  57 + return "\"valueRange\":{\"min\":0,\"max\":65535}";
  58 + }
  59 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT32_AB_CD.name())||
  60 + OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT32_CD_AB.name())||
  61 + OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT32_BA_DC.name())||
  62 + OriginalDataType.equals(AttributeSourceDataTypeEnum.UINT32_DC_BA.name())){
  63 + return "\"valueRange\":{\"min\":0,\"max\":4294967295}";
  64 + }
  65 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.STRING.name())){
  66 + return "";
  67 + }
  68 + if(OriginalDataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())||
  69 + OriginalDataType.equals(AttributeSourceDataTypeEnum.BITS.name())){
  70 + return "\"valueRange\":{\"min\":0,\"max\":1}";
  71 + }
  72 + return "\"valueRange\":{\"min\":-2147483648,\"max\":2147483647}";
  73 + }
  74 +
  75 + public static String getOperationTypeIsRw(String operationType){
  76 + String []rw = operationType.split("_");
  77 + return rw[1].equals("r")?"r":"rw";
  78 + }
  79 +
  80 + public static Boolean getOperationTypeIsWriteOnly(String operationType){
  81 + String rw = getOperationTypeIsRw(operationType);
  82 + if(rw.equals("r")){//只读为false
  83 + return false;
  84 + }
  85 + return true;
  86 + }
  87 +}
@@ -3,6 +3,7 @@ package org.thingsboard.server.common.data.device.profile; @@ -3,6 +3,7 @@ package org.thingsboard.server.common.data.device.profile;
3 import lombok.Data; 3 import lombok.Data;
4 import org.thingsboard.server.common.data.DeviceTransportType; 4 import org.thingsboard.server.common.data.DeviceTransportType;
5 import org.thingsboard.server.common.data.validation.NoXss; 5 import org.thingsboard.server.common.data.validation.NoXss;
  6 +import org.thingsboard.server.common.data.yunteng.enums.ProtocolAnalysisEnum;
6 7
7 @Data 8 @Data
8 public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { 9 public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration {
@@ -26,6 +27,12 @@ public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTr @@ -26,6 +27,12 @@ public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTr
26 @NoXss 27 @NoXss
27 private String downScriptId; 28 private String downScriptId;
28 29
  30 + /**
  31 + * 协议: 默认自定义
  32 + */
  33 + @NoXss
  34 + private ProtocolAnalysisEnum protocol = ProtocolAnalysisEnum.CUSTOM;
  35 +
29 @Override 36 @Override
30 public DeviceTransportType getType() { 37 public DeviceTransportType getType() {
31 return DeviceTransportType.TCP; 38 return DeviceTransportType.TCP;
@@ -37,7 +37,36 @@ public interface FastIotConstants { @@ -37,7 +37,36 @@ public interface FastIotConstants {
37 String ORGANIZATION = "organization"; 37 String ORGANIZATION = "organization";
38 String SCENE_REACT = "sceneReact"; 38 String SCENE_REACT = "sceneReact";
39 } 39 }
40 - 40 + interface ModBusRTU{
  41 + /**
  42 + * 原始数据类型
  43 + */
  44 + String ORIGINAL_DATA_TYPE = "originalDataType";
  45 + /**
  46 + * 寄存器地址
  47 + */
  48 + String REGISTER_ADDRESS = "registerAddress";
  49 +
  50 + /**
  51 + * 操作功能码类型
  52 + */
  53 + String OPERATION_TYPE = "operationType";
  54 +
  55 + /**
  56 + * 缩放因子
  57 + */
  58 + String SCALE_FACTOR = "scaling";
  59 +
  60 + /**
  61 + * 原始hex的标识符
  62 + */
  63 + String SOURCE_DATA_IDENTIFY = "source";
  64 +
  65 + /**
  66 + * 比特位置
  67 + */
  68 + String BIT_MASK = "bitMask";
  69 + }
41 interface TBCacheConfig { 70 interface TBCacheConfig {
42 String TB_CACHE_CONFIG_KEY = "TB_CONNECT_CACHE"; 71 String TB_CACHE_CONFIG_KEY = "TB_CONNECT_CACHE";
43 String EXISTING_TENANT = "EXISTING_TENANT"; 72 String EXISTING_TENANT = "EXISTING_TENANT";
@@ -120,6 +120,7 @@ public enum ErrorMessage { @@ -120,6 +120,7 @@ public enum ErrorMessage {
120 EZVIZ_API_ERROR(400095,"荧石视频获取TokenAPI调用失败【%s】,错误码【%s】"), 120 EZVIZ_API_ERROR(400095,"荧石视频获取TokenAPI调用失败【%s】,错误码【%s】"),
121 EZVIZ_GET_URL_ERROR(400096,"荧石API调用获取URL失败!!"), 121 EZVIZ_GET_URL_ERROR(400096,"荧石API调用获取URL失败!!"),
122 IMPORT_TCP_ERROR(400097,"TCP产品不能导入INT,DOUBLE,BOOL,TEXT以外的数据类型属性!!"), 122 IMPORT_TCP_ERROR(400097,"TCP产品不能导入INT,DOUBLE,BOOL,TEXT以外的数据类型属性!!"),
  123 + IMPORT_ERROR(400098,"请使用模板excel重新导入"),
123 HAVE_NO_PERMISSION(500002,"没有修改权限"), 124 HAVE_NO_PERMISSION(500002,"没有修改权限"),
124 NOT_ALLOED_ISOLATED_IN_MONOLITH(500003,"【monolith】模式下,不能选择【isolated】类型的租户配置"); 125 NOT_ALLOED_ISOLATED_IN_MONOLITH(500003,"【monolith】模式下,不能选择【isolated】类型的租户配置");
125 126
@@ -2,6 +2,7 @@ package org.thingsboard.server.common.data.yunteng.dto; @@ -2,6 +2,7 @@ package org.thingsboard.server.common.data.yunteng.dto;
2 2
3 import io.swagger.annotations.ApiModelProperty; 3 import io.swagger.annotations.ApiModelProperty;
4 import lombok.Data; 4 import lombok.Data;
  5 +import org.thingsboard.server.common.data.yunteng.enums.HexByteOrderEnum;
5 import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType; 6 import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType;
6 import java.io.Serializable; 7 import java.io.Serializable;
7 import java.util.List; 8 import java.util.List;
@@ -28,4 +29,7 @@ public class TkDeviceRpcDTO implements Serializable { @@ -28,4 +29,7 @@ public class TkDeviceRpcDTO implements Serializable {
28 29
29 @ApiModelProperty(value = "寄存器值") 30 @ApiModelProperty(value = "寄存器值")
30 private List<Integer> registerValues; 31 private List<Integer> registerValues;
  32 +
  33 + @ApiModelProperty(value = "顺序")
  34 + private HexByteOrderEnum hexByteOrderEnum;
31 } 35 }
@@ -32,6 +32,7 @@ public class TkThingsModel implements Serializable { @@ -32,6 +32,7 @@ public class TkThingsModel implements Serializable {
32 32
33 private JsonNode functionJson; 33 private JsonNode functionJson;
34 34
  35 + private JsonNode extensionDesc;
35 36
36 private Integer status; 37 private Integer status;
37 38
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +/** 物模型的原始数据类型 */
  4 +public enum AttributeSourceDataTypeEnum {
  5 + /** 16位有符号整数AB */
  6 + INT16_AB("16位有符号整数AB"),
  7 + /** 16位有符号整数BA */
  8 + INT16_BA("16位有符号整数BA"),
  9 + /** 16位无符号整数AB */
  10 + UINT16_AB("16位无符号整数AB"),
  11 + /** 16位无符号整数BA */
  12 + UINT16_BA("16位无符号整数BA"),
  13 + /** 32位有符号整数AB_CD */
  14 + INT32_AB_CD("32位有符号整数AB_CD"),
  15 + /** 32位有符号整数CD_AB */
  16 + INT32_CD_AB("32位有符号整数CD_AB"),
  17 + /** 32位有符号整数BA_DC */
  18 + INT32_BA_DC("32位有符号整数BA_DC"),
  19 + /** 32位有符号整数DC_BA */
  20 + INT32_DC_BA("32位有符号整数DC_BA"),
  21 + /** 32位无符号整数AB_CD */
  22 + UINT32_AB_CD("32位无符号整数AB_CD"),
  23 + /** 32位无符号整数CD_AB */
  24 + UINT32_CD_AB("32位无符号整数CD_AB"),
  25 + /** 32位无符号整数BA_DC */
  26 + UINT32_BA_DC("32位无符号整数BA_DC"),
  27 + /** 32位无符号整数DC_BA */
  28 + UINT32_DC_BA("32位无符号整数DC_BA"),
  29 + /** 单精度浮点型AB_CD */
  30 + FLOAT_AB_CD("单精度浮点型AB_CD"),
  31 + /** 单精度浮点型CD_AB */
  32 + FLOAT_CD_AB("单精度浮点型CD_AB"),
  33 + /** 单精度浮点型BA_DC */
  34 + FLOAT_BA_DC("单精度浮点型BA_DC"),
  35 + /** 单精度浮点型AB_CD */
  36 + FLOAT_DC_BA("单精度浮点型AB_CD"),
  37 + /** 双精度浮点型 */
  38 + DOUBLE("双精度浮点型"),
  39 + /** 字符串 */
  40 + STRING("字符串"),
  41 + /** 布尔型 */
  42 + BOOLEAN("布尔型"),
  43 + /** 位 */
  44 + BITS("位");
  45 +
  46 + String label;
  47 +
  48 + AttributeSourceDataTypeEnum(String label) {
  49 + this.label = label;
  50 + }
  51 +
  52 + public static String getName(String label) {
  53 + for (AttributeSourceDataTypeEnum type : values()) {
  54 + if (type.label.equals(label)) {
  55 + return type.name();
  56 + }
  57 + }
  58 + return null;
  59 + }
  60 +}
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +/**
  4 + * hex字节顺序
  5 + */
  6 +public enum HexByteOrderEnum {
  7 + AB,
  8 + BA,
  9 + AB_CD,
  10 + CD_AB,
  11 + BA_DC,
  12 + DC_BA
  13 +}
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +/** 物模型的原始操作类型 */
  4 +public enum OperationTypeEnum {
  5 + inputStatus_r_02("离散量输入_只读0x02"),
  6 + coilStatus_r_01("线圈状态_只读0x01"),
  7 + coilStatus_rw_01_05("线圈状态_读写读取使用0x01写入使用0x05"),
  8 + coilStatus_rw_01_0F("线圈状态_读写读取使用0x01写入使用0x0F"),
  9 + coilStatus_w_05("线圈状态_只写0x05"),
  10 + coilStatus_w_0F("线圈状态_只写0x0F"),
  11 + holdingRegister_r_03("保持寄存器_只读0x03"),
  12 + holdingRegister_rw_03_06("保持寄存器_读写读取使用0x03写入使用0x06"),
  13 + holdingRegister_rw_03_10("保持寄存器_读写读取使用0x03写入使用0x10"),
  14 + holdingRegister_w_06("保持寄存器_只写0x06"),
  15 + holdingRegister_w_10("保持寄存器_只写0x10"),
  16 + inputRegister_r_04("输入寄存器_只读0x04");
  17 + String label;
  18 +
  19 + OperationTypeEnum(String label) {
  20 + this.label = label;
  21 + }
  22 + public static String getName(String label) {
  23 + for (OperationTypeEnum type : values()) {
  24 + if (type.label.equals(label)) {
  25 + return type.name();
  26 + }
  27 + }
  28 + return null;
  29 + }
  30 +}
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +/**
  4 + * 协议解析枚举值
  5 + */
  6 +public enum ProtocolAnalysisEnum {
  7 + MODBUS_RTU,
  8 + CUSTOM
  9 +}
@@ -9,6 +9,7 @@ public enum TkRpcStatus { @@ -9,6 +9,7 @@ public enum TkRpcStatus {
9 EXPIRED("已过期"), 9 EXPIRED("已过期"),
10 FAILED("响应失败"), 10 FAILED("响应失败"),
11 DELETED("已删除"); 11 DELETED("已删除");
  12 +
12 String label; 13 String label;
13 14
14 TkRpcStatus(String label) { 15 TkRpcStatus(String label) {
@@ -3,40 +3,41 @@ package org.thingsboard.server.common.data.yunteng.utils; @@ -3,40 +3,41 @@ package org.thingsboard.server.common.data.yunteng.utils;
3 import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType; 3 import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType;
4 4
5 public class CrcUtils { 5 public class CrcUtils {
6 -/**  
7 - * crc16 for modbus  
8 - * @param buf buffer to be crc  
9 - * @return crc result word  
10 - */  
11 - static int alex_crc16(TkModBusCheckType algorithm, byte[] buf) {  
12 - int i, j;  
13 - int c, crc = algorithm.getInit();  
14 - for (i = 0; i < buf.length; i++) {  
15 - c = buf[i] & 0x00FF;//原始数据高8位和初始值进行异或运算保持不变  
16 - crc ^= c;  
17 - for (j = 0; j < 8; j++) {//原始数据左移8位  
18 - if ((crc & 0x0001) != 0) {  
19 - crc >>= 1;  
20 - crc ^= algorithm.getXorIn();//把处理之后的数据和多项式进行模2除法,求得余数  
21 - } else  
22 - crc >>= 1;  
23 - }  
24 - }  
25 - return (crc);  
26 - }  
27 -/**  
28 - * convert string to buffer and append the crc check word to the end of buffer  
29 - * @param toSend string to be convert  
30 - * @return buffer with crc word, high byte if after low byte according the modbus  
31 - */  
32 - public static String getSendBuf(TkModBusCheckType algorithm, String toSend) {  
33 - byte[] bb = ByteUtils.hexToBytes(toSend);  
34 - int ri = alex_crc16(algorithm,bb);  
35 - if(!algorithm.getByteOrder()){  
36 - ri=ByteUtils.revert(ri);  
37 - }  
38 - byte[] crc = ByteUtils.getBytes((short)ri);  
39 - return ByteUtils.bytesToHex(crc);  
40 - } 6 + /**
  7 + * crc16 for modbus
  8 + *
  9 + * @param buf buffer to be crc
  10 + * @return crc result word
  11 + */
  12 + static int alex_crc16(TkModBusCheckType algorithm, byte[] buf) {
  13 + int i, j;
  14 + int c, crc = algorithm.getInit();
  15 + for (i = 0; i < buf.length; i++) {
  16 + c = buf[i] & 0x00FF; // 原始数据高8位和初始值进行异或运算保持不变
  17 + crc ^= c;
  18 + for (j = 0; j < 8; j++) { // 原始数据左移8位
  19 + if ((crc & 0x0001) != 0) {
  20 + crc >>= 1;
  21 + crc ^= algorithm.getXorIn(); // 把处理之后的数据和多项式进行模2除法,求得余数
  22 + } else crc >>= 1;
  23 + }
  24 + }
  25 + return (crc);
  26 + }
41 27
  28 + /**
  29 + * convert string to buffer and append the crc check word to the end of buffer
  30 + *
  31 + * @param toSend string to be convert
  32 + * @return buffer with crc word, high byte if after low byte according the modbus
  33 + */
  34 + public static String getSendBuf(TkModBusCheckType algorithm, String toSend) {
  35 + byte[] bb = ByteUtils.hexToBytes(toSend);
  36 + int ri = alex_crc16(algorithm, bb);
  37 + if (!algorithm.getByteOrder()) {
  38 + ri = ByteUtils.revert(ri);
  39 + }
  40 + byte[] crc = ByteUtils.getBytes((short) ri);
  41 + return ByteUtils.bytesToHex(crc);
  42 + }
42 } 43 }
  1 +package org.thingsboard.server.common.data.yunteng.utils;
  2 +
  3 +import org.thingsboard.server.common.data.yunteng.enums.AttributeSourceDataTypeEnum;
  4 +import org.thingsboard.server.common.data.yunteng.enums.HexByteOrderEnum;
  5 +import java.nio.ByteOrder;
  6 +import java.text.DecimalFormat;
  7 +
  8 +public class HexConvertUtils {
  9 + /**
  10 + * 16位有符号、无符号HEX转为short
  11 + * 顺序为AB
  12 + *
  13 + * @param hexStr 16进制HEX,长度4
  14 + * @return short值
  15 + */
  16 + public static short int16OrUint16ToShort(String hexStr) {
  17 + hexStr = replaceHexStr(hexStr);
  18 + // 先转换为无符号16位整数(int类型)
  19 + int unsignedValue = Integer.parseInt(hexStr, 16);
  20 + // 对于负数,通过补码计算还原实际的负数值
  21 + short signedValue;
  22 + if ((unsignedValue & 0x8000) != 0) {
  23 + // 最高位为1,则该数值是一个负数
  24 + signedValue = (short) (unsignedValue - 0x10000);
  25 + } else {
  26 + // 最高位为0,则该数值是一个正数,可以直接转换为short类型
  27 + signedValue = (short) unsignedValue;
  28 + }
  29 + return signedValue;
  30 + }
  31 +
  32 + /**
  33 + * 32位有符号、无符号HEX转long
  34 + * 顺序位AB_CD
  35 + * @param hexValue 16进制HEX,长度8
  36 + * @return long值
  37 + */
  38 + public static long int32OrUint32ToLong(String hexValue){
  39 + hexValue = replaceHexStr(hexValue);
  40 + long unsignedIntValue = Integer.parseUnsignedInt(hexValue, 16);
  41 + // 计算实际的有符号32位整数值
  42 + long signedIntValue;
  43 + if ((unsignedIntValue & 0x80000000) == 0) {
  44 + // 如果最高位是0,则直接转换为正数
  45 + signedIntValue = unsignedIntValue;
  46 + } else {
  47 + // 如果最高位是1,则需要取反加1得到原码,然后再转换为负数
  48 + signedIntValue = -((unsignedIntValue ^ 0xFFFFFFFF) + 1);
  49 + }
  50 + return signedIntValue;
  51 + }
  52 +
  53 + /**
  54 + * 将16进制转位2进制倒序,并返回int数组 用于寄存器的位计算
  55 + * @param hex 16进制hex
  56 + * @param binaryLength 二进制长度
  57 + * @return int数组
  58 + */
  59 + public static int[] hexToBinaryPaddedAndReversed(String hex, int binaryLength) {
  60 + // 将16进制字符串转换为整数
  61 + int number = Integer.parseInt(hex, 16);
  62 +
  63 + // 将整数转换为二进制字符串
  64 + String binaryString = Integer.toBinaryString(number);
  65 +
  66 + // 检查是否需要补位
  67 + while (binaryString.length() < binaryLength) {
  68 + binaryString = "0" + binaryString;
  69 + }
  70 +
  71 + // 对二进制字符串进行反转
  72 + char[] chars = binaryString.toCharArray();
  73 + StringBuilder reversedBinaryString = new StringBuilder();
  74 + for (int i = chars.length - 1; i >= 0; i--) {
  75 + reversedBinaryString.append(chars[i]);
  76 + }
  77 + int[] result = new int[binaryLength];
  78 + for(int i = 0; i < reversedBinaryString.length(); i++) {
  79 + result[i] = Integer.valueOf(reversedBinaryString.substring(i,i+1));
  80 + }
  81 + return result;
  82 + }
  83 +
  84 + /**
  85 + * 把数据的HEX位置调整好
  86 + *
  87 + * @param hex 原始hex
  88 + * @param dataTypeEnum 属性类型
  89 + * @return 排序后的hex
  90 + */
  91 + public static String convertHexOrder(String hex, AttributeSourceDataTypeEnum dataTypeEnum) {
  92 + HexByteOrderEnum hexByteOrderEnum;
  93 + switch (dataTypeEnum) {
  94 + case FLOAT_AB_CD:
  95 + case FLOAT_BA_DC:
  96 + case FLOAT_CD_AB:
  97 + case FLOAT_DC_BA:
  98 + hexByteOrderEnum = HexByteOrderEnum.valueOf(dataTypeEnum.name().split("FLOAT_")[1]);
  99 + break;
  100 + case INT16_AB:
  101 + case UINT16_AB:
  102 + case INT16_BA:
  103 + case UINT16_BA:
  104 + hexByteOrderEnum = HexByteOrderEnum.valueOf(dataTypeEnum.name().split("_")[1]);
  105 + break;
  106 + case INT32_AB_CD:
  107 + case UINT32_AB_CD:
  108 + case INT32_BA_DC:
  109 + case UINT32_BA_DC:
  110 + case INT32_CD_AB:
  111 + case UINT32_CD_AB:
  112 + case INT32_DC_BA:
  113 + case UINT32_DC_BA:
  114 + hexByteOrderEnum = HexByteOrderEnum.valueOf(dataTypeEnum.name().split("32_")[1]);
  115 + break;
  116 + default:
  117 + hexByteOrderEnum = HexByteOrderEnum.AB;
  118 + }
  119 + return convertHex(hex, hexByteOrderEnum);
  120 + }
  121 +
  122 + public static String convertHex(String hex, HexByteOrderEnum hexByteOrderEnum) {
  123 + String result;
  124 + switch (hexByteOrderEnum) {
  125 + case AB:
  126 + result = processHexH(hex);
  127 + break;
  128 + case BA:
  129 + result = processHexL(hex);
  130 + break;
  131 + case BA_DC:
  132 + result = processHexL(hex.substring(0, 4)) + processHexL(hex.substring(4, 8));
  133 + break;
  134 + case CD_AB:
  135 + result = processHexH(hex.substring(4, 8)) + processHexH(hex.substring(0, 4));
  136 + break;
  137 + case DC_BA:
  138 + result = processHexL(hex.substring(4, 8)) + processHexL(hex.substring(0, 4));
  139 + break;
  140 + case AB_CD:
  141 + result = processHexH(hex.substring(0, 4)) + processHexH(hex.substring(4, 8));
  142 + break;
  143 + default:
  144 + result = "";
  145 + }
  146 + return result;
  147 + }
  148 +
  149 + private static String processHexL(String hex) {
  150 + String l = hex.substring(0, 2);
  151 + String h = hex.substring(2, 4);
  152 + return h + l;
  153 + }
  154 +
  155 + private static String processHexH(String hex) {
  156 + String h = hex.substring(0, 2);
  157 + String l = hex.substring(2, 4);
  158 + return h + l;
  159 + }
  160 +
  161 + /**
  162 + * 一个字节hex转为byte值
  163 + *
  164 + * @param hex 16进制hex,长度2
  165 + * @return byte值
  166 + */
  167 + private static byte hexToByte(String hex) {
  168 + hex = replaceHexStr(hex);
  169 + return (byte) Integer.parseInt(hex, 16);
  170 + }
  171 +
  172 + /**
  173 + * 16进制转为位进行计算
  174 + *
  175 + * @param hex 16进制HEX,长度为2
  176 + * @return int数组
  177 + */
  178 + public static int[] hexToBits(String hex) {
  179 + byte value = hexToByte(hex);
  180 + int[] bits = new int[8];
  181 + for (int i = 0; i < 8; i++) {
  182 + bits[i] = (value >> i) & 1;
  183 + }
  184 + return bits;
  185 + }
  186 +
  187 + /**
  188 + * 16进制转为boolean进行计算
  189 + *
  190 + * @param hex hex 16进制HEX,长度为2
  191 + * @return boolean数组
  192 + */
  193 + public static boolean[] hexToBooleans(String hex) {
  194 +
  195 + byte value = hexToByte(hex);
  196 + boolean[] booleans = new boolean[8];
  197 + for (int i = 0; i < 8; i++) {
  198 + booleans[i] = (value & (1 << i)) != 0;
  199 + }
  200 + return booleans;
  201 + }
  202 +
  203 + /**
  204 + * 16进制转为Ascii码
  205 + *
  206 + * @param hex 16进制
  207 + * @return ascii字符串
  208 + */
  209 + public static String hexToASCII(String hex) {
  210 + hex = replaceHexStr(hex);
  211 + StringBuilder asciiString = new StringBuilder();
  212 + for (int i = 0; i < hex.length(); i += 2) {
  213 + String byteValueStr = hex.substring(i, i + 2);
  214 + int byteValue = Integer.parseInt(byteValueStr, 16); // 将HEX转为十进制
  215 +
  216 + // 对于ASCII范围内的值(0-127),直接转换
  217 + if (byteValue >= 0 && byteValue <= 127) {
  218 + asciiString.append((char) byteValue);
  219 + }
  220 + // 对于超过ASCII范围的值,我们将其转换为16进制字符串表示
  221 + else {
  222 + asciiString.append("0x").append(byteValueStr);
  223 + }
  224 + }
  225 + return asciiString.toString();
  226 + }
  227 +
  228 + public static Object setDeviceTelemetry(
  229 + String hex,
  230 + AttributeSourceDataTypeEnum dataTypeEnum,
  231 + int bitAddress,
  232 + float scaleFactor) {
  233 + Object value = new Object();
  234 + DecimalFormat intFormat = new DecimalFormat("#.00");
  235 + switch (dataTypeEnum) {
  236 + case FLOAT_AB_CD:
  237 + case FLOAT_BA_DC:
  238 + case FLOAT_CD_AB:
  239 + case FLOAT_DC_BA:
  240 + hex = convertHexOrder(hex, dataTypeEnum);
  241 + value =
  242 + intFormat.format(
  243 + IEEE754.parseSinglePrecisionHex(hex, ByteOrder.BIG_ENDIAN) * scaleFactor);
  244 + break;
  245 + case DOUBLE:
  246 + value =
  247 + intFormat.format(
  248 + IEEE754.parseDoublePrecisionHex(hex, ByteOrder.BIG_ENDIAN) * scaleFactor);
  249 + break;
  250 + case INT16_AB:
  251 + case UINT16_AB:
  252 + case INT16_BA:
  253 + case UINT16_BA:
  254 + hex = convertHexOrder(hex, dataTypeEnum);
  255 + value = intFormat.format(int16OrUint16ToShort(hex) * scaleFactor);
  256 + break;
  257 + case INT32_AB_CD:
  258 + case UINT32_AB_CD:
  259 + case INT32_BA_DC:
  260 + case UINT32_BA_DC:
  261 + case INT32_CD_AB:
  262 + case UINT32_CD_AB:
  263 + case INT32_DC_BA:
  264 + case UINT32_DC_BA:
  265 + hex = convertHexOrder(hex, dataTypeEnum);
  266 + value = intFormat.format(int32OrUint32ToLong(hex) * scaleFactor);
  267 + break;
  268 + case STRING:
  269 + value = hexToASCII(hex);
  270 + break;
  271 + case BITS:
  272 + value = hexToBinaryPaddedAndReversed(hex,hex.length()/2*8)[bitAddress];
  273 + break;
  274 + case BOOLEAN:
  275 + value = hexToBooleans(hex)[bitAddress];
  276 + break;
  277 + }
  278 +
  279 + return value;
  280 + }
  281 +
  282 + private static String replaceHexStr(String hex) {
  283 + return hex.trim().replaceAll(" ", "");
  284 + }
  285 +
  286 +}
  1 +package org.thingsboard.server.common.data.yunteng.utils;
  2 +
  3 +import java.nio.ByteBuffer;
  4 +import java.nio.ByteOrder;
  5 +
  6 +public class IEEE754 {
  7 + /**
  8 + * 定义方法用于解析单精度浮点数
  9 + *
  10 + * @param hex 32位单精度HEX
  11 + * @param byteOrder 字节序
  12 + * @return 单精度浮点数
  13 + */
  14 + public static float parseSinglePrecisionHex(String hex, ByteOrder byteOrder) {
  15 + byte[] bytes = ByteUtils.hexStr2Bytes(hex);
  16 +
  17 + if (bytes.length != 4) {
  18 + throw new IllegalArgumentException(
  19 + "Invalid length for single precision float, expected 4 bytes");
  20 + }
  21 +
  22 + return ByteBuffer.wrap(bytes).order(byteOrder).getFloat();
  23 + }
  24 +
  25 + /**
  26 + * 定义方法用于解析双精度浮点数
  27 + *
  28 + * @param hex 64位双精度HEX
  29 + * @param byteOrder 字节序
  30 + * @return 双精度浮点数
  31 + */
  32 + public static double parseDoublePrecisionHex(String hex, ByteOrder byteOrder) {
  33 + byte[] bytes = ByteUtils.hexStr2Bytes(hex);
  34 +
  35 + if (bytes.length != 8) {
  36 + throw new IllegalArgumentException(
  37 + "Invalid length for double precision float, expected 8 bytes");
  38 + }
  39 +
  40 + return ByteBuffer.wrap(bytes).order(byteOrder).getDouble();
  41 + }
  42 +}
  1 +package org.thingsboard.server.common.data.yunteng.utils;
  2 +
  3 +import org.thingsboard.server.common.data.StringUtils;
  4 +import org.thingsboard.server.common.data.yunteng.enums.AttributeSourceDataTypeEnum;
  5 +import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType;
  6 +import java.util.ArrayList;
  7 +import java.util.List;
  8 +
  9 +public class ModbusUtils {
  10 + /**
  11 + * 校验上报的HEX是否为标准Modbus格式 标准的Modbus帧包括:设备地址、功能码、数据和校验码
  12 + *
  13 + * @param hexString 上报的HEX
  14 + * @return true满足 false不满足
  15 + */
  16 + public static boolean isValidModbusResponseFrame(String hexString) {
  17 + if (StringUtils.isEmpty(hexString)) {
  18 + return false;
  19 + }
  20 + // 去除可能的空格
  21 + hexString = hexString.trim().replaceAll(" ", "");
  22 +
  23 + // 判断长度是否为偶数且至少为6(我们只考虑ASCII帧)
  24 + if (hexString.length() % 2 != 0 || hexString.length() < 6) {
  25 + return false;
  26 + }
  27 +
  28 + // 获取从机地址、功能码和数据
  29 + String slaveAddress = hexString.substring(0, 2);
  30 + String functionCode = hexString.substring(2, 4);
  31 + String data = hexString.substring(4, hexString.length() - 4);
  32 +
  33 + // 计算校验码
  34 + String calculatedCRC =
  35 + CrcUtils.getSendBuf(TkModBusCheckType.CRC_16_LOWER, slaveAddress + functionCode + data);
  36 +
  37 + // 获取并验证校验码
  38 + String crc = hexString.substring(hexString.length() - 4);
  39 + if (!calculatedCRC.equals(crc)) {
  40 + return false;
  41 + }
  42 +
  43 + // 检查功能码是否有效
  44 + if (!isValidFunctionCode(functionCode)) {
  45 + return false;
  46 + }
  47 + return isValidData(data);
  48 + }
  49 +
  50 + private static boolean isValidFunctionCode(String functionCode) {
  51 + switch (functionCode) {
  52 + // 读取线圈状态
  53 + case "01":
  54 + return true;
  55 + // 读取输入状态
  56 + case "02":
  57 + return true;
  58 + // 读取保持寄存器
  59 + case "03":
  60 + return true;
  61 + // 读取输入寄存器
  62 + case "04":
  63 + return true;
  64 + default:
  65 + return false;
  66 + }
  67 + }
  68 +
  69 + /**
  70 + * 截取字符串
  71 + *
  72 + * @param str 原始字符串
  73 + * @param length 截取长度
  74 + * @return 截取后的有序列表
  75 + */
  76 + public static List<String> splitString(String str, int length) {
  77 + List<String> result = new ArrayList<>();
  78 + for (int i = 0; i < str.length(); i += length) {
  79 + if (i + length <= str.length()) {
  80 + result.add(str.substring(i, i + length));
  81 + } else {
  82 + result.add(str.substring(i));
  83 + }
  84 + }
  85 + return result;
  86 + }
  87 +
  88 + /**
  89 + * 根据原始数据类型,确认HEX的截取长度
  90 + *
  91 + * @param dataTypeEnum 原始数据类型
  92 + * @return 截取长度
  93 + */
  94 + public static int getDataLengthBySourceDataType(AttributeSourceDataTypeEnum dataTypeEnum) {
  95 + int length;
  96 + switch (dataTypeEnum) {
  97 + case UINT16_AB:
  98 + case UINT16_BA:
  99 + case INT16_AB:
  100 + case INT16_BA:
  101 + length = 4;
  102 + break;
  103 + case UINT32_AB_CD:
  104 + case UINT32_DC_BA:
  105 + case UINT32_BA_DC:
  106 + case UINT32_CD_AB:
  107 + case INT32_AB_CD:
  108 + case INT32_DC_BA:
  109 + case INT32_BA_DC:
  110 + case INT32_CD_AB:
  111 + case FLOAT_AB_CD:
  112 + case FLOAT_BA_DC:
  113 + case FLOAT_CD_AB:
  114 + case FLOAT_DC_BA:
  115 + length = 8;
  116 + break;
  117 + case DOUBLE:
  118 + length = 16;
  119 + break;
  120 + default:
  121 + length = 2;
  122 + }
  123 + return length;
  124 + }
  125 +
  126 + private static boolean isValidData(String data) {
  127 + // 寄存器字节数长度
  128 + int dataLength = Integer.parseInt(data.substring(0, 2), 16);
  129 + // 实际数据长度
  130 + int realDataLength = data.substring(2, data.length()).length() / 2;
  131 + return dataLength == realDataLength;
  132 + }
  133 +}
@@ -23,6 +23,8 @@ import static org.thingsboard.server.common.transport.service.DefaultTransportSe @@ -23,6 +23,8 @@ import static org.thingsboard.server.common.transport.service.DefaultTransportSe
23 import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; 23 import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN;
24 24
25 import com.fasterxml.jackson.databind.JsonNode; 25 import com.fasterxml.jackson.databind.JsonNode;
  26 +import com.fasterxml.jackson.databind.node.ObjectNode;
  27 +import com.fasterxml.jackson.databind.node.TextNode;
26 import com.google.common.util.concurrent.FutureCallback; 28 import com.google.common.util.concurrent.FutureCallback;
27 import com.google.common.util.concurrent.Futures; 29 import com.google.common.util.concurrent.Futures;
28 import com.google.common.util.concurrent.ListenableFuture; 30 import com.google.common.util.concurrent.ListenableFuture;
@@ -34,23 +36,17 @@ import io.netty.channel.ChannelHandlerContext; @@ -34,23 +36,17 @@ import io.netty.channel.ChannelHandlerContext;
34 import io.netty.channel.ChannelInboundHandlerAdapter; 36 import io.netty.channel.ChannelInboundHandlerAdapter;
35 import io.netty.handler.codec.mqtt.*; 37 import io.netty.handler.codec.mqtt.*;
36 import io.netty.handler.ssl.SslHandler; 38 import io.netty.handler.ssl.SslHandler;
37 -import io.netty.util.CharsetUtil;  
38 import io.netty.util.ReferenceCountUtil; 39 import io.netty.util.ReferenceCountUtil;
39 import io.netty.util.concurrent.Future; 40 import io.netty.util.concurrent.Future;
40 import io.netty.util.concurrent.GenericFutureListener; 41 import io.netty.util.concurrent.GenericFutureListener;
41 import java.io.IOException; 42 import java.io.IOException;
42 import java.net.InetSocketAddress; 43 import java.net.InetSocketAddress;
43 -import java.util.List;  
44 -import java.util.Map;  
45 -import java.util.Optional;  
46 -import java.util.UUID; 44 +import java.util.*;
47 import java.util.concurrent.atomic.AtomicInteger; 45 import java.util.concurrent.atomic.AtomicInteger;
48 -import java.util.regex.Pattern; 46 +
49 import lombok.extern.slf4j.Slf4j; 47 import lombok.extern.slf4j.Slf4j;
50 import org.apache.commons.lang3.StringUtils; 48 import org.apache.commons.lang3.StringUtils;
51 import org.checkerframework.checker.nullness.qual.Nullable; 49 import org.checkerframework.checker.nullness.qual.Nullable;
52 -import org.springframework.boot.context.event.ApplicationReadyEvent;  
53 -import org.springframework.context.event.EventListener;  
54 import org.thingsboard.common.util.JacksonUtil; 50 import org.thingsboard.common.util.JacksonUtil;
55 import org.thingsboard.server.common.data.DataConstants; 51 import org.thingsboard.server.common.data.DataConstants;
56 import org.thingsboard.server.common.data.Device; 52 import org.thingsboard.server.common.data.Device;
@@ -59,9 +55,11 @@ import org.thingsboard.server.common.data.DeviceTransportType; @@ -59,9 +55,11 @@ import org.thingsboard.server.common.data.DeviceTransportType;
59 import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration; 55 import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration;
60 import org.thingsboard.server.common.data.id.DeviceId; 56 import org.thingsboard.server.common.data.id.DeviceId;
61 import org.thingsboard.server.common.data.rpc.RpcStatus; 57 import org.thingsboard.server.common.data.rpc.RpcStatus;
62 -import org.thingsboard.server.common.data.yunteng.enums.StatusEnum;  
63 -import org.thingsboard.server.common.data.yunteng.enums.TkScriptFunctionType;  
64 -import org.thingsboard.server.common.data.yunteng.utils.ByteUtils; 58 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  59 +import org.thingsboard.server.common.data.yunteng.dto.TkThingsModel;
  60 +import org.thingsboard.server.common.data.yunteng.enums.AttributeSourceDataTypeEnum;
  61 +import org.thingsboard.server.common.data.yunteng.enums.ProtocolAnalysisEnum;
  62 +import org.thingsboard.server.common.data.yunteng.utils.*;
65 import org.thingsboard.server.common.msg.tools.TbRateLimitsException; 63 import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
66 import org.thingsboard.server.common.transport.SessionMsgListener; 64 import org.thingsboard.server.common.transport.SessionMsgListener;
67 import org.thingsboard.server.common.transport.TransportService; 65 import org.thingsboard.server.common.transport.TransportService;
@@ -199,15 +197,72 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements @@ -199,15 +197,72 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements
199 if (!checkConnected(ctx, tcpMessage)) { 197 if (!checkConnected(ctx, tcpMessage)) {
200 return; 198 return;
201 } 199 }
  200 + //判断协议类型
  201 + TkTcpDeviceProfileTransportConfiguration transportConfiguration =
  202 + (TkTcpDeviceProfileTransportConfiguration) deviceSessionCtx.getDeviceProfile().getProfileData().getTransportConfiguration();
  203 + switch (transportConfiguration.getProtocol())
  204 + {
  205 + case CUSTOM:
  206 + customScriptProcess(ctx,tcpMessage);
  207 + break;
  208 + case MODBUS_RTU:
  209 + modbusRtuProcess(ctx,tcpMessage);
  210 + break;
  211 +
  212 + }
  213 + }
  214 +
  215 +
  216 + private void modbusRtuProcess(ChannelHandlerContext ctx, String tcpMessage){
  217 + //移除空格
  218 + String hexString = tcpMessage.trim().replaceAll(" ","");
  219 + //判断是否为16进制HEX
  220 + if(hexString.matches("-?[0-9a-fA-F]+")){
  221 + boolean modbusCheckResult = ModbusUtils.isValidModbusResponseFrame(hexString);
  222 + //判断是否满足modbus标准,满足的才处理不满足的过滤掉
  223 + if(modbusCheckResult){
  224 + //根据上报的地址码,判断该条消息归属于那个设备的数据
  225 + String deviceAddress = hexString.substring(0,2);
  226 + int deviceCode = Integer.parseInt(deviceSessionCtx.getDeviceCode(),16);
  227 + int deviceAddressCode = Integer.parseInt(deviceAddress,16);
  228 + if(gatewaySessionHandler != null && deviceCode != deviceAddressCode){
  229 + gatewaySessionHandler.onDeviceTelemetry(deviceAddress, null, hexString,
  230 + ProtocolAnalysisEnum.MODBUS_RTU);
  231 + }else{
  232 + if(deviceCode == deviceAddressCode){
  233 + processCustomDirectDeviceMsg(ctx, deviceSessionCtx.getPayloadAdaptor()
  234 + .convertModbusHexToPublish(deviceSessionCtx,hexString).get());
  235 + }
  236 + }
  237 + }
  238 + }
  239 + }
  240 +
  241 +
  242 +
  243 +
  244 + private void customScriptProcess(ChannelHandlerContext ctx, String tcpMessage){
202 deviceSessionCtx.doUpScript(tcpMessage, r -> { 245 deviceSessionCtx.doUpScript(tcpMessage, r -> {
203 - if (gatewaySessionHandler != null) {  
204 - processGatewayDeviceMsg(ctx, r); 246 + //根据网关上报的消息,判断消息的来源是否为网关子设备,判断依据deviceCode即设备地址码或设备标识符
  247 + if (gatewaySessionHandler != null && checkMessageIsFromSensor(r.getDatas())) {
  248 + processCustomGatewayDeviceMsg(ctx, r);
  249 + }else{
  250 + processCustomDirectDeviceMsg(ctx, r);
205 } 251 }
206 - processDirectDeviceMsg(ctx, r);  
207 }); 252 });
208 -  
209 } 253 }
210 254
  255 + private boolean checkMessageIsFromSensor(Map<String,Object> dataMap){
  256 + boolean isSensorMessage = true;
  257 + String gateWayDeviceCode = deviceSessionCtx.getDeviceCode();
  258 + for (Map.Entry<String,Object> entry :dataMap.entrySet()){
  259 + if(entry.getKey().equals(gateWayDeviceCode)){
  260 + isSensorMessage = false;
  261 + break;
  262 + }
  263 + }
  264 + return isSensorMessage;
  265 + }
211 266
212 /** 267 /**
213 * 上行脚本解析结果是否包含数据 268 * 上行脚本解析结果是否包含数据
@@ -221,8 +276,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements @@ -221,8 +276,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements
221 return true; 276 return true;
222 } 277 }
223 278
224 -  
225 - private void processGatewayDeviceMsg(ChannelHandlerContext ctx, TcpUpEntry tcpMessage) { 279 + private void processCustomGatewayDeviceMsg(ChannelHandlerContext ctx, TcpUpEntry tcpMessage) {
226 log.trace("[{}][{}] Processing publish msg [{}]!", sessionId, deviceSessionCtx.getDeviceId(), tcpMessage); 280 log.trace("[{}][{}] Processing publish msg [{}]!", sessionId, deviceSessionCtx.getDeviceId(), tcpMessage);
227 try { 281 try {
228 Map<String, Object> datas = tcpMessage.getDatas(); 282 Map<String, Object> datas = tcpMessage.getDatas();
@@ -232,7 +286,8 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements @@ -232,7 +286,8 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements
232 return; 286 return;
233 } 287 }
234 if (tcpMessage.getTelemetry()) { 288 if (tcpMessage.getTelemetry()) {
235 - gatewaySessionHandler.onDeviceTelemetry(devName, tcpMessage.getRequestId(), param.toString()); 289 + gatewaySessionHandler.onDeviceTelemetry(devName, tcpMessage.getRequestId(), param.toString(),
  290 + ProtocolAnalysisEnum.CUSTOM);
236 } else { 291 } else {
237 // gatewaySessionHandler.onDeviceRpcResponse(devName, tcpMessage.getRequestId(), param.toString()); 292 // gatewaySessionHandler.onDeviceRpcResponse(devName, tcpMessage.getRequestId(), param.toString());
238 } 293 }
@@ -248,7 +303,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements @@ -248,7 +303,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements
248 } 303 }
249 } 304 }
250 305
251 - private void processDirectDeviceMsg(ChannelHandlerContext ctx, TcpUpEntry tcpMessage) { 306 + private void processCustomDirectDeviceMsg(ChannelHandlerContext ctx, TcpUpEntry tcpMessage) {
252 log.trace("[{}][{}] Processing publish msg [{}]!", sessionId, deviceSessionCtx.getDeviceId(), tcpMessage); 307 log.trace("[{}][{}] Processing publish msg [{}]!", sessionId, deviceSessionCtx.getDeviceId(), tcpMessage);
253 try { 308 try {
254 TcpTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); 309 TcpTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
@@ -435,7 +490,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements @@ -435,7 +490,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements
435 } else { 490 } else {
436 DeviceProfile profile = msg.getDeviceProfile(); 491 DeviceProfile profile = msg.getDeviceProfile();
437 TkTcpDeviceProfileTransportConfiguration tcpConfig = (TkTcpDeviceProfileTransportConfiguration) profile.getProfileData().getTransportConfiguration(); 492 TkTcpDeviceProfileTransportConfiguration tcpConfig = (TkTcpDeviceProfileTransportConfiguration) profile.getProfileData().getTransportConfiguration();
438 - if (scriptId != null && !tcpConfig.getAuthScriptId().equals(scriptId.toString())) { 493 + if (scriptId != null&& tcpConfig.getProtocol().equals(ProtocolAnalysisEnum.CUSTOM) && !tcpConfig.getAuthScriptId().equals(scriptId.toString())) {
439 authedCounter.incrementAndGet(); 494 authedCounter.incrementAndGet();
440 return; 495 return;
441 } 496 }
@@ -562,6 +617,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements @@ -562,6 +617,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements
562 @Override 617 @Override
563 public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) { 618 public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) {
564 deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); 619 deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt);
  620 + deviceSessionCtx.setDeviceCode(JacksonUtil.toString(device.getAdditionalInfo()));
565 } 621 }
566 622
567 @Override 623 @Override
@@ -15,9 +15,9 @@ @@ -15,9 +15,9 @@
15 */ 15 */
16 package org.thingsboard.server.transport.tcp.adaptors; 16 package org.thingsboard.server.transport.tcp.adaptors;
17 17
18 -import com.google.common.util.concurrent.Futures;  
19 -import com.google.common.util.concurrent.ListenableFuture;  
20 -import com.google.common.util.concurrent.MoreExecutors; 18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.node.ObjectNode;
  20 +import com.fasterxml.jackson.databind.node.TextNode;
21 import com.google.gson.JsonElement; 21 import com.google.gson.JsonElement;
22 import com.google.gson.JsonObject; 22 import com.google.gson.JsonObject;
23 import com.google.gson.JsonParser; 23 import com.google.gson.JsonParser;
@@ -29,6 +29,11 @@ import org.springframework.stereotype.Component; @@ -29,6 +29,11 @@ import org.springframework.stereotype.Component;
29 import org.springframework.util.StringUtils; 29 import org.springframework.util.StringUtils;
30 import org.thingsboard.common.util.JacksonUtil; 30 import org.thingsboard.common.util.JacksonUtil;
31 import org.thingsboard.server.common.data.ota.OtaPackageType; 31 import org.thingsboard.server.common.data.ota.OtaPackageType;
  32 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  33 +import org.thingsboard.server.common.data.yunteng.dto.TkThingsModel;
  34 +import org.thingsboard.server.common.data.yunteng.enums.AttributeSourceDataTypeEnum;
  35 +import org.thingsboard.server.common.data.yunteng.utils.HexConvertUtils;
  36 +import org.thingsboard.server.common.data.yunteng.utils.ModbusUtils;
32 import org.thingsboard.server.common.transport.adaptor.AdaptorException; 37 import org.thingsboard.server.common.transport.adaptor.AdaptorException;
33 import org.thingsboard.server.common.transport.adaptor.JsonConverter; 38 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
34 import org.thingsboard.server.gen.transport.TransportProtos; 39 import org.thingsboard.server.gen.transport.TransportProtos;
@@ -38,6 +43,8 @@ import java.nio.charset.Charset; @@ -38,6 +43,8 @@ import java.nio.charset.Charset;
38 import java.nio.charset.StandardCharsets; 43 import java.nio.charset.StandardCharsets;
39 import java.util.*; 44 import java.util.*;
40 import java.util.concurrent.ExecutionException; 45 import java.util.concurrent.ExecutionException;
  46 +import java.util.stream.Collectors;
  47 +import java.util.stream.IntStream;
41 48
42 49
43 /** 50 /**
@@ -50,6 +57,9 @@ public class JsonTcpAdaptor implements TcpTransportAdaptor { @@ -50,6 +57,9 @@ public class JsonTcpAdaptor implements TcpTransportAdaptor {
50 protected static final Charset UTF8 = StandardCharsets.UTF_8; 57 protected static final Charset UTF8 = StandardCharsets.UTF_8;
51 private static final JsonParser parser = new JsonParser(); 58 private static final JsonParser parser = new JsonParser();
52 59
  60 + private static final String registerCountKey = "registerCountKey";
  61 + private static final String modelsKey = "modelsKey";
  62 +
53 63
54 @Override 64 @Override
55 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(TcpDeviceWareSessionContext ctx, String payload) throws AdaptorException { 65 public TransportProtos.PostTelemetryMsg convertToPostTelemetry(TcpDeviceWareSessionContext ctx, String payload) throws AdaptorException {
@@ -164,6 +174,352 @@ public class JsonTcpAdaptor implements TcpTransportAdaptor { @@ -164,6 +174,352 @@ public class JsonTcpAdaptor implements TcpTransportAdaptor {
164 return Optional.of(null); 174 return Optional.of(null);
165 } 175 }
166 176
  177 + @Override
  178 + public Optional<TcpUpEntry> convertModbusHexToPublish(TcpDeviceWareSessionContext ctx, String hexString) {
  179 + List<TkThingsModel> thingsModels = ctx.getDeviceProfile().getProfileData().getThingsModel();
  180 + TcpUpEntry entry = new TcpUpEntry(UUID.randomUUID());
  181 + entry.setTelemetry(true);
  182 + ObjectNode convertResult =convertHexToJson(hexString,thingsModels);
  183 + if(null == convertResult){
  184 + convertResult = JacksonUtil.newObjectNode();
  185 + }
  186 + //是否包含原始数据标识符
  187 + if(checkContainsSourceDataIdentify(thingsModels)){
  188 + convertResult.put(FastIotConstants.ModBusRTU.SOURCE_DATA_IDENTIFY,hexString);
  189 + }
  190 + entry.setDatas(JacksonUtil.convertValue(convertResult,Map.class));
  191 + return Optional.of(entry);
  192 + }
  193 +
  194 + private ObjectNode convertHexToJson(String hexString, List<TkThingsModel> thingsModels){
  195 + if(null !=thingsModels && !thingsModels.isEmpty()){
  196 + //功能码
  197 + String functionCode = hexString.substring(2, 4);
  198 + //数据长度hex
  199 + String dataLength = hexString.substring(4,6);
  200 + boolean coilOrInputStatus = functionCode.equals("01") || functionCode.equals("02");
  201 + int byteLength = Integer.parseInt(dataLength,16);
  202 + //返回的数据长度
  203 + int responseByteLength = coilOrInputStatus?byteLength:byteLength/2;
  204 + //根据功能码,获取匹配的物模型属性,并将连续的寄存器地址物模型分为一个集合,例如 40001、40002 40100、40101、40102
  205 + Map<String,Map<String,Object>> matchThingsModelsMap = matchFunctionCodeList(functionCode,thingsModels);
  206 +
  207 + for (Map.Entry<String, Map<String, Object>> entry :matchThingsModelsMap.entrySet()){
  208 + Map<String,Object> matchMap = entry.getValue();
  209 + //匹配的寄存器数量即物模型属性数量
  210 + int matchRegisterCount = (int) matchMap.get(registerCountKey);
  211 + if(matchRegisterCount>0){
  212 + List<TkThingsModel> matchThingsModels = (List<TkThingsModel>) matchMap.get(modelsKey);
  213 + if(coilOrInputStatus){
  214 + //去掉字节长度后的数据
  215 + String registerHexData =hexString.substring(6,hexString.length()-4);
  216 + //一个字节8个位,用字节长度*8即可得到对应多少个位的值
  217 + int bitNumber = responseByteLength * 8;
  218 + if(matchRegisterCount<= bitNumber){
  219 + //线圈状态和离散量输入的数据读取处理
  220 + return coilOrInputStatusDataProcess(registerHexData,matchThingsModels);
  221 + }
  222 + }else{
  223 + //如果连续物模型的长度等于返回的寄存器长度,就用该连续的物模型进行匹配
  224 + boolean compareResult = matchThingsModelsMap.size()>1?matchRegisterCount == responseByteLength
  225 + :matchRegisterCount <= responseByteLength || responseByteLength <= matchRegisterCount;
  226 + if(compareResult){
  227 + //保持寄存器和输入寄存器的数据读取处理
  228 + return holdingOrInputRegisterDataProcess(hexString,matchThingsModels,thingsModels,functionCode);
  229 + }
  230 + }
  231 + }
  232 + }
  233 + }
  234 + return null;
  235 + }
  236 +
  237 + private ObjectNode coilOrInputStatusDataProcess(String registerHexData,List<TkThingsModel> matchThingsModels){
  238 + if(null != matchThingsModels && !matchThingsModels.isEmpty()){
  239 + int groupSize = 2; // 设定分组大小
  240 + //分组后的hex
  241 + List<String> groups = IntStream.range(0, registerHexData.length())
  242 + .filter(i -> i % groupSize == 0)
  243 + .mapToObj(i -> registerHexData.substring(i, Math.min(registerHexData.length(), i + groupSize)))
  244 + .collect(Collectors.toList());
  245 + List<Integer> allBits = new ArrayList<>();
  246 + for (String byteHex : groups){
  247 + int[] bits = HexConvertUtils.hexToBits(byteHex);
  248 + List<Integer> temp = Arrays.stream(bits).boxed().collect(Collectors.toList());
  249 + allBits.addAll(temp);
  250 + }
  251 + ObjectNode packageData = JacksonUtil.newObjectNode();
  252 + matchThingsModels.stream().forEach(model->{
  253 + //扩展描述信息
  254 + JsonNode extensionDescInfo = model.getExtensionDesc();
  255 + //找到物模型的寄存器地址,并转为10进制
  256 + int registerAddress = Integer.parseInt(String.valueOf(extensionDescInfo.get(
  257 + FastIotConstants.ModBusRTU.REGISTER_ADDRESS).asText()),16);
  258 + packageData.put(model.getIdentifier(), allBits.get(registerAddress));
  259 + });
  260 + return packageData;
  261 + }
  262 + return null;
  263 + }
  264 + private boolean checkContainsSourceDataIdentify(List<TkThingsModel> thingsModels){
  265 + boolean containsSourceDataIdentify = false;
  266 + for (TkThingsModel model : thingsModels){
  267 + if(model.getIdentifier().equals(FastIotConstants.ModBusRTU.SOURCE_DATA_IDENTIFY)){
  268 + containsSourceDataIdentify = true;
  269 + break;
  270 + }
  271 + }
  272 + return containsSourceDataIdentify;
  273 + }
  274 + private ObjectNode holdingOrInputRegisterDataProcess(String hexString, List<TkThingsModel> matchThingsModels,
  275 + List<TkThingsModel> sourceThingsModels,String functionCode){
  276 + Map<String,Object> dataJson = new HashMap<>();
  277 + //上报hex的寄存器个数
  278 + int hexRegisterCount = Integer.parseInt(hexString.substring(4,6),16)/2;
  279 + //去掉字节长度后的数据
  280 + String registerHexData =hexString.substring(6,hexString.length()-4);
  281 + //当前物模型的数据截取起始地址
  282 + int currentHexStartAddress = 0;
  283 + //处理了的寄存器个数
  284 + int processCount = 0;
  285 + //开始构造数据结构
  286 + for (TkThingsModel thingsModel : matchThingsModels){
  287 + //说明上报的hex数据已经处理完了
  288 + if(processCount>=hexRegisterCount){
  289 + break;
  290 + }
  291 + //扩展描述信息
  292 + JsonNode extensionDescInfo = thingsModel.getExtensionDesc();
  293 + String identifier = thingsModel.getIdentifier();
  294 + AttributeSourceDataTypeEnum dataTypeEnum = JacksonUtil.convertValue(extensionDescInfo.get(
  295 + FastIotConstants.ModBusRTU.ORIGINAL_DATA_TYPE).asText(),AttributeSourceDataTypeEnum.class);
  296 + //对数据进行封装
  297 + //根据物模型的原始数据类型,返回长度
  298 + int hexLength = ModbusUtils.getDataLengthBySourceDataType(dataTypeEnum);
  299 +
  300 + int bitAddress = 0;
  301 + boolean isBits = dataTypeEnum.equals(AttributeSourceDataTypeEnum.BITS);
  302 + if(isBits){
  303 + bitAddress = extensionDescInfo.get(FastIotConstants.ModBusRTU.BIT_MASK).asInt();
  304 + hexLength = 4;
  305 + }
  306 + String hex = registerHexData.substring(currentHexStartAddress,currentHexStartAddress + hexLength);
  307 + currentHexStartAddress += hexLength;
  308 + if(isBits){
  309 + //位计算,一个寄存器可以表示16个位
  310 + //找到所有满足的功能码,地址码的物模型
  311 + int sourceAddress = Integer.parseInt(extensionDescInfo.get(FastIotConstants.ModBusRTU.REGISTER_ADDRESS).asText(),16);
  312 + bitProcess(hex,functionCode,sourceAddress,dataJson,sourceThingsModels);
  313 + }else{
  314 + float scaleFactor = (1/Float.valueOf(extensionDescInfo.get(FastIotConstants.ModBusRTU.SCALE_FACTOR).asText()));
  315 + Object value = HexConvertUtils.setDeviceTelemetry(hex,dataTypeEnum,bitAddress,scaleFactor);
  316 + if(dataTypeEnum.name().contains("UINT") && Double.valueOf(value.toString())<0){
  317 + //如果无符号返回的值小于0则不保存
  318 + continue;
  319 + }
  320 + dataJson.put(identifier,value);
  321 + }
  322 + processCount+=getRegisterCountByDataType(extensionDescInfo
  323 + .get(FastIotConstants.ModBusRTU.ORIGINAL_DATA_TYPE).asText());
  324 + }
  325 + return JacksonUtil.convertValue(dataJson, ObjectNode.class);
  326 + }
  327 +
  328 + /**
  329 + * 位计算处理,一个寄存器16个位计算
  330 + * @param hex 16进制数据
  331 + * @param sourceFunctionCode 功能码
  332 + * @param sourceAddress 寄存器地址
  333 + * @param sourceThingsModels 所有的物模型
  334 + * @return 处理后的json数据格式
  335 + */
  336 + private void bitProcess(String hex,String sourceFunctionCode,int sourceAddress,Map<String,Object> dataJson,
  337 + List<TkThingsModel> sourceThingsModels){
  338 + List<TkThingsModel> matchResult = functionCodeAndAddress(sourceThingsModels,sourceFunctionCode,sourceAddress);
  339 + if(!matchResult.isEmpty()){
  340 + for (TkThingsModel tkThingsModel : matchResult){
  341 + int bitAddress = tkThingsModel.getExtensionDesc().get(FastIotConstants.ModBusRTU.BIT_MASK).asInt();
  342 + Object value = HexConvertUtils.setDeviceTelemetry(hex,AttributeSourceDataTypeEnum.BITS,
  343 + bitAddress,0);
  344 + dataJson.put(tkThingsModel.getIdentifier(),value);
  345 + }
  346 + }
  347 + }
  348 + private List<TkThingsModel> functionCodeAndAddress(List<TkThingsModel> sourceThingsModels,
  349 + String sourceFunctionCode,int sourceAddress){
  350 + List<TkThingsModel> matchResult = new ArrayList<>();
  351 + for (TkThingsModel thingsModel : sourceThingsModels){
  352 + TextNode operationType = (TextNode) thingsModel.getExtensionDesc().get(FastIotConstants.ModBusRTU.OPERATION_TYPE);
  353 + if(null != operationType && !StringUtils.isEmpty(operationType.asText())){
  354 + //功能码,根据不同情况进行截取
  355 + String[] codes = operationType.asText().split("_");
  356 + //rw r w 这三种情况
  357 + String readWrite = codes[1];
  358 + //是否包含r操作
  359 + if(readWrite.contains("r")){
  360 + String checkFunctionCode = codes[2];
  361 + if(sourceFunctionCode.equals(checkFunctionCode)){
  362 + //获取寄存器地址,将16进制hex转为10进制
  363 + TextNode registerAddress = (TextNode) thingsModel.getExtensionDesc().get(FastIotConstants.ModBusRTU.REGISTER_ADDRESS);
  364 + if(null != registerAddress && !StringUtils.isEmpty(registerAddress.asText())){
  365 + int address = Integer.parseInt(registerAddress.asText(),16);
  366 + if(address == sourceAddress){
  367 + matchResult.add(thingsModel);
  368 + }
  369 + }
  370 + }
  371 + }
  372 + }
  373 + }
  374 + return matchResult;
  375 + }
  376 + /**
  377 + * 根据上报的功能码与物模型里面的功能码进行匹配,并将连续的寄存器地址进行合并
  378 + * 存在以下这种情况:
  379 + * 40001、40002 40100、40101、40102
  380 + * @param functionCode 上报的功能码
  381 + * @param thingsModels 物模型
  382 + * @return 返回的数据结构
  383 + * [{
  384 + * "1group": {
  385 + * "registerCountKey": 2,//寄存器数量
  386 + * "modelsKey": [TKThingsModel,TKThingsModel]
  387 + * }
  388 + * },
  389 + * {
  390 + * "2group": {
  391 + * "registerCountKey": 2,//寄存器数量
  392 + * "modelsKey": [TKThingsModel,TKThingsModel]
  393 + * }
  394 + * }
  395 + * ]
  396 + */
  397 + private Map<String,Map<String,Object>> matchFunctionCodeList(
  398 + String functionCode, List<TkThingsModel> thingsModels) {
  399 + //按连续寄存器地址进行分组的map
  400 + Map<String,Map<String,Object>> groupMap = new HashMap<>();
  401 + List<Map.Entry<Integer, TkThingsModel>> matchFunctionModels = matchFunctionCode(thingsModels,functionCode);
  402 + //按连续寄存器地址进行区分
  403 + if(!matchFunctionModels.isEmpty()){
  404 + int flag = -1;
  405 + String prevDataType = null;
  406 + List<TkThingsModel> modelsList = null;
  407 + int registerCount = 0;
  408 + Map<String,Object> tempMap = new HashMap<>();
  409 + String groupKey = "group";
  410 + for(Map.Entry<Integer, TkThingsModel> orderMap :matchFunctionModels){
  411 + TkThingsModel thingsModel = orderMap.getValue();
  412 + int key = orderMap.getKey();
  413 + if(null==prevDataType){
  414 + prevDataType = thingsModel.getExtensionDesc()
  415 + .get(FastIotConstants.ModBusRTU.ORIGINAL_DATA_TYPE).asText();
  416 + }
  417 + if(flag!=-1 && null != modelsList){
  418 + //默认寄存器地址+1
  419 + int add = getRegisterCountByDataType(prevDataType);
  420 + //如果当前key是连续寄存器地址,择直接存入temp里面
  421 + if(key == flag + add){
  422 + Map<String,Object> saveMap = groupMap.get(groupKey);
  423 + modelsList = (List<TkThingsModel>) saveMap.get(modelsKey);
  424 + registerCount = (int) saveMap.get(registerCountKey);
  425 + modelsList.add(thingsModel);
  426 + //寄存器数量,直接加上当前
  427 + registerCount +=getRegisterCountByDataType(thingsModel.getExtensionDesc()
  428 + .get(FastIotConstants.ModBusRTU.ORIGINAL_DATA_TYPE).asText());
  429 + flag = key;
  430 + }else{
  431 + //重置临时map为新的对象
  432 + tempMap = new HashMap<>();
  433 + modelsList = new ArrayList<>();
  434 + registerCount = 0;
  435 + modelsList.add(thingsModel);
  436 + prevDataType = thingsModel.getExtensionDesc()
  437 + .get(FastIotConstants.ModBusRTU.ORIGINAL_DATA_TYPE).asText();
  438 + registerCount +=getRegisterCountByDataType(prevDataType);
  439 + flag = key;
  440 + groupKey+=groupMap.size();
  441 + }
  442 + }else{
  443 + //初始化每个分组的信息
  444 + modelsList = new ArrayList<>();
  445 + modelsList.add(thingsModel);
  446 + registerCount +=getRegisterCountByDataType(prevDataType);
  447 + flag = key;
  448 + groupKey+=groupMap.size();
  449 + }
  450 + prevDataType = thingsModel.getExtensionDesc().get(FastIotConstants.ModBusRTU.ORIGINAL_DATA_TYPE).asText();
  451 + tempMap.put(registerCountKey,registerCount);
  452 + tempMap.put(modelsKey,modelsList);
  453 + groupMap.put(groupKey,tempMap);
  454 + }
  455 + }
  456 + return groupMap.size()==1?groupMap:orderByMatchMap(groupMap);
  457 + }
  458 + private Map<String,Map<String,Object>> orderByMatchMap(Map<String,Map<String,Object>> groupMap){
  459 + // 使用自定义的Comparator按count排序
  460 + return groupMap.entrySet().stream()
  461 + .sorted(Map.Entry.comparingByValue((v1, v2) ->
  462 + v1.get(registerCountKey).equals(v2.get(registerCountKey)) ? 0 :
  463 + Integer.compare(Integer.valueOf(v2.get(registerCountKey).toString()),
  464 + Integer.valueOf(v1.get(registerCountKey).toString()))))
  465 + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
  466 + (oldValue, newValue) -> oldValue, LinkedHashMap::new));
  467 + }
  468 + private int getRegisterCountByDataType(String dataType){
  469 + int registerCount = 1;
  470 + if(dataType.contains("INT32") || dataType.contains("FLOAT")){
  471 + registerCount = 2;
  472 + }
  473 + if(dataType.contains("INT16")||dataType.equals(AttributeSourceDataTypeEnum.BOOLEAN.name())
  474 + ||dataType.equals(AttributeSourceDataTypeEnum.BITS.name())){
  475 + registerCount = 1;
  476 + }
  477 + if(dataType.equals(AttributeSourceDataTypeEnum.DOUBLE.name())){
  478 + registerCount =4;
  479 + }
  480 + return registerCount;
  481 + }
  482 +
  483 + /**
  484 + * 根据上报的功能码与物模型里面的功能码进行匹配,并按寄存器进行排序
  485 + * 存在以下几种情况:
  486 + * holdingRegister_w_06
  487 + * holdingRegister_r_03
  488 + * holdingRegister_rw_03_06
  489 + * @param functionCode 上报的功能码
  490 + * @param thingsModels 物模型
  491 + * @return 匹配的物模型
  492 + */
  493 + private List<Map.Entry<Integer, TkThingsModel>> matchFunctionCode(List<TkThingsModel> thingsModels,String functionCode){
  494 + Map<Integer, TkThingsModel> matchThingsModel = new HashMap<>();
  495 + for (TkThingsModel thingsModel : thingsModels){
  496 + TextNode operationType = (TextNode) thingsModel.getExtensionDesc().get(FastIotConstants.ModBusRTU.OPERATION_TYPE);
  497 + if(null != operationType && !StringUtils.isEmpty(operationType.asText())){
  498 + //功能码,根据不同情况进行截取
  499 + String[] codes = operationType.asText().split("_");
  500 + //rw r w 这三种情况
  501 + String readWrite = codes[1];
  502 + //是否包含r操作
  503 + if(readWrite.contains("r")){
  504 + String checkFunctionCode = codes[2];
  505 + if(functionCode.equals(checkFunctionCode)){
  506 + //获取寄存器地址,将16进制hex转为10进制
  507 + TextNode registerAddress = (TextNode) thingsModel.getExtensionDesc().get(FastIotConstants.ModBusRTU.REGISTER_ADDRESS);
  508 + if(null != registerAddress && !StringUtils.isEmpty(registerAddress.asText())){
  509 + int address = Integer.parseInt(registerAddress.asText(),16);
  510 + matchThingsModel.put(address,thingsModel);
  511 + }
  512 + }
  513 + }
  514 + }
  515 + }
  516 + // 将Map转换为List
  517 + List<Map.Entry<Integer, TkThingsModel>> list = new ArrayList<>(matchThingsModel.entrySet());
  518 + // 对List进行排序
  519 + Collections.sort(list, Map.Entry.comparingByKey());
  520 + return list;
  521 + }
  522 +
167 public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { 523 public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException {
168 String payload = validatePayload(sessionId, payloadData, false); 524 String payload = validatePayload(sessionId, payloadData, false);
169 try { 525 try {
@@ -267,6 +623,4 @@ public class JsonTcpAdaptor implements TcpTransportAdaptor { @@ -267,6 +623,4 @@ public class JsonTcpAdaptor implements TcpTransportAdaptor {
267 // return new MqttPublishMessage(mqttFixedHeader, header, payload); 623 // return new MqttPublishMessage(mqttFixedHeader, header, payload);
268 return null; 624 return null;
269 } 625 }
270 -  
271 -  
272 } 626 }
@@ -78,6 +78,8 @@ public interface TcpTransportAdaptor { @@ -78,6 +78,8 @@ public interface TcpTransportAdaptor {
78 78
79 Optional<TcpUpEntry> convertToPublish(TcpDeviceWareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException; 79 Optional<TcpUpEntry> convertToPublish(TcpDeviceWareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException;
80 80
  81 + Optional<TcpUpEntry> convertModbusHexToPublish(TcpDeviceWareSessionContext ctx,String hexString);
  82 +
81 public static byte[] toBytes(ByteBuf inbound) { 83 public static byte[] toBytes(ByteBuf inbound) {
82 byte[] bytes = new byte[inbound.readableBytes()]; 84 byte[] bytes = new byte[inbound.readableBytes()];
83 int readerIndex = inbound.readerIndex(); 85 int readerIndex = inbound.readerIndex();
1 package org.thingsboard.server.transport.tcp.session; 1 package org.thingsboard.server.transport.tcp.session;
2 2
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import com.fasterxml.jackson.databind.ObjectMapper;
  5 +import com.fasterxml.jackson.databind.node.ObjectNode;
3 import com.google.common.util.concurrent.FutureCallback; 6 import com.google.common.util.concurrent.FutureCallback;
4 import com.google.common.util.concurrent.Futures; 7 import com.google.common.util.concurrent.Futures;
5 import com.google.common.util.concurrent.ListenableFuture; 8 import com.google.common.util.concurrent.ListenableFuture;
6 import com.google.common.util.concurrent.MoreExecutors; 9 import com.google.common.util.concurrent.MoreExecutors;
7 import lombok.Getter; 10 import lombok.Getter;
  11 +import lombok.Setter;
8 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
9 import org.apache.commons.lang3.StringUtils; 13 import org.apache.commons.lang3.StringUtils;
10 import org.jetbrains.annotations.Nullable; 14 import org.jetbrains.annotations.Nullable;
@@ -12,6 +16,8 @@ import org.thingsboard.common.util.JacksonUtil; @@ -12,6 +16,8 @@ import org.thingsboard.common.util.JacksonUtil;
12 import org.thingsboard.server.common.data.DeviceProfile; 16 import org.thingsboard.server.common.data.DeviceProfile;
13 import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; 17 import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
14 import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration; 18 import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration;
  19 +import org.thingsboard.server.common.data.yunteng.constant.FastIotConstants;
  20 +import org.thingsboard.server.common.data.yunteng.enums.ProtocolAnalysisEnum;
15 import org.thingsboard.server.common.data.yunteng.enums.TkScriptFunctionType; 21 import org.thingsboard.server.common.data.yunteng.enums.TkScriptFunctionType;
16 import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; 22 import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
17 import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; 23 import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
@@ -40,7 +46,14 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont @@ -40,7 +46,14 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont
40 private volatile UUID telemetryScriptId; 46 private volatile UUID telemetryScriptId;
41 @Getter 47 @Getter
42 private volatile UUID rpcScriptId; 48 private volatile UUID rpcScriptId;
43 - private final ConcurrentMap<String, Integer> rpcAwaitingAck; 49 +
  50 + /**
  51 + * 设备地址码或设备标识符
  52 + */
  53 + @Getter
  54 + private volatile String deviceCode;
  55 + private final ConcurrentMap<String
  56 + , Integer> rpcAwaitingAck;
44 /** 57 /**
45 * 设备唯一标识符,例如:设备SN、设备地址码等。数据内携带标识符 58 * 设备唯一标识符,例如:设备SN、设备地址码等。数据内携带标识符
46 */ 59 */
@@ -65,6 +78,7 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont @@ -65,6 +78,7 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont
65 public void setDeviceInfo(TransportDeviceInfo deviceInfo) { 78 public void setDeviceInfo(TransportDeviceInfo deviceInfo) {
66 super.setDeviceInfo(deviceInfo); 79 super.setDeviceInfo(deviceInfo);
67 deviceName = deviceInfo.getDeviceName(); 80 deviceName = deviceInfo.getDeviceName();
  81 + setDeviceCode(deviceInfo.getAdditionalInfo());
68 } 82 }
69 83
70 @Override 84 @Override
@@ -86,10 +100,25 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont @@ -86,10 +100,25 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont
86 if(null != tcpConfiguration && StringUtils.isNotEmpty(tcpConfiguration.getAuthScriptId())){ 100 if(null != tcpConfiguration && StringUtils.isNotEmpty(tcpConfiguration.getAuthScriptId())){
87 this.authScriptId = UUID.fromString(tcpConfiguration.getAuthScriptId()); 101 this.authScriptId = UUID.fromString(tcpConfiguration.getAuthScriptId());
88 } 102 }
89 - this.telemetryScriptId = UUID.fromString(tcpConfiguration.getUpScriptId()); 103 + if( tcpConfiguration.getProtocol().equals(ProtocolAnalysisEnum.CUSTOM)){
  104 + this.telemetryScriptId = UUID.fromString(tcpConfiguration.getUpScriptId());
  105 + }
90 106
91 } 107 }
92 108
  109 + public void setDeviceCode(String additionalInfo){
  110 + try{
  111 + ObjectMapper objectMapper = new ObjectMapper();
  112 + JsonNode additionalInfoJson = objectMapper.readTree(additionalInfo);
  113 + if(null != additionalInfoJson && !additionalInfoJson.isEmpty()){
  114 + deviceCode = String.valueOf(additionalInfoJson.get(FastIotConstants.TCP_DEVICE_IDENTIFY_FILED)==null?
  115 + null:additionalInfoJson.get(FastIotConstants.TCP_DEVICE_IDENTIFY_FILED).asText());
  116 + }
  117 + }catch (Exception e){
  118 + log.error("device [{}] convert additionalInfo error",deviceName);
  119 + }
  120 + }
  121 +
93 122
94 123
95 /** 124 /**
@@ -33,6 +33,8 @@ import org.springframework.util.ConcurrentReferenceHashMap; @@ -33,6 +33,8 @@ import org.springframework.util.ConcurrentReferenceHashMap;
33 import org.springframework.util.StringUtils; 33 import org.springframework.util.StringUtils;
34 import org.thingsboard.common.util.JacksonUtil; 34 import org.thingsboard.common.util.JacksonUtil;
35 import org.thingsboard.server.common.data.DeviceProfile; 35 import org.thingsboard.server.common.data.DeviceProfile;
  36 +import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration;
  37 +import org.thingsboard.server.common.data.yunteng.enums.ProtocolAnalysisEnum;
36 import org.thingsboard.server.common.data.yunteng.utils.ByteUtils; 38 import org.thingsboard.server.common.data.yunteng.utils.ByteUtils;
37 import org.thingsboard.server.common.transport.TransportService; 39 import org.thingsboard.server.common.transport.TransportService;
38 import org.thingsboard.server.common.transport.TransportServiceCallback; 40 import org.thingsboard.server.common.transport.TransportServiceCallback;
@@ -44,6 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro @@ -44,6 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro
44 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; 46 import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
45 import org.thingsboard.server.transport.tcp.TcpTransportContext; 47 import org.thingsboard.server.transport.tcp.TcpTransportContext;
46 import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor; 48 import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor;
  49 +import org.thingsboard.server.transport.tcp.adaptors.TcpUpEntry;
47 import org.thingsboard.server.transport.tcp.util.ByteBufUtils; 50 import org.thingsboard.server.transport.tcp.util.ByteBufUtils;
48 51
49 import javax.annotation.Nullable; 52 import javax.annotation.Nullable;
@@ -94,19 +97,24 @@ public class TcpGatewaySessionHandler { @@ -94,19 +97,24 @@ public class TcpGatewaySessionHandler {
94 } 97 }
95 98
96 99
97 - public void onDeviceTelemetry(String deviceName, UUID requestId, String deviceDataStr) { 100 + public void onDeviceTelemetry(String deviceName, UUID requestId, String sourceHexData, ProtocolAnalysisEnum analysisEnum) {
98 Futures.addCallback(checkDeviceConnected(deviceName), 101 Futures.addCallback(checkDeviceConnected(deviceName),
99 new FutureCallback<>() { 102 new FutureCallback<>() {
100 @Override 103 @Override
101 public void onSuccess(@Nullable TcpGatewayDeviceSessionCtx deviceCtx) { 104 public void onSuccess(@Nullable TcpGatewayDeviceSessionCtx deviceCtx) {
102 - deviceCtx.doUpScript(deviceDataStr, r -> {  
103 - try {  
104 - TransportProtos.PostTelemetryMsg postTelemetryMsg = deviceCtx.getPayloadAdaptor().convertToPostTelemetry(deviceCtx, JacksonUtil.toString(r.getDatas()));  
105 - processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, requestId);  
106 - } catch (AdaptorException e) {  
107 - log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, deviceDataStr, e);  
108 - }  
109 - }); 105 + switch (analysisEnum){
  106 + case CUSTOM:
  107 + deviceCtx.doUpScript(sourceHexData, r -> {
  108 + processDeviceTelemetry(deviceCtx,deviceName,requestId,sourceHexData,JacksonUtil.toString(r.getDatas()));
  109 + });
  110 + break;
  111 + case MODBUS_RTU:
  112 + TcpUpEntry entry = deviceCtx.getPayloadAdaptor().convertModbusHexToPublish(deviceCtx,sourceHexData).get();
  113 + if(null != entry && !entry.getDatas().isEmpty()){
  114 + processDeviceTelemetry(deviceCtx,deviceName,requestId,sourceHexData,JacksonUtil.toString(entry.getDatas()));
  115 + }
  116 + break;
  117 + }
110 } 118 }
111 119
112 @Override 120 @Override
@@ -116,6 +124,16 @@ public class TcpGatewaySessionHandler { @@ -116,6 +124,16 @@ public class TcpGatewaySessionHandler {
116 }, context.getExecutor()); 124 }, context.getExecutor());
117 } 125 }
118 126
  127 + private void processDeviceTelemetry (TcpGatewayDeviceSessionCtx deviceCtx,String deviceName, UUID requestId, String sourceHexData,
  128 + String telemetryData){
  129 + try {
  130 + TransportProtos.PostTelemetryMsg postTelemetryMsg = deviceCtx.getPayloadAdaptor().convertToPostTelemetry(deviceCtx, telemetryData);
  131 + processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, requestId);
  132 + } catch (AdaptorException e) {
  133 + log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, sourceHexData, e);
  134 + }
  135 + }
  136 +
119 137
120 public void onGatewayDisconnect() { 138 public void onGatewayDisconnect() {
121 devices.forEach(this::deregisterSession); 139 devices.forEach(this::deregisterSession);
@@ -332,6 +350,7 @@ public class TcpGatewaySessionHandler { @@ -332,6 +350,7 @@ public class TcpGatewaySessionHandler {
332 350
333 351
334 private void deregisterSession(String deviceName, TcpGatewayDeviceSessionCtx deviceSessionCtx) { 352 private void deregisterSession(String deviceName, TcpGatewayDeviceSessionCtx deviceSessionCtx) {
  353 + devices.remove(deviceName);
335 transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); 354 transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
336 transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null); 355 transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null);
337 log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName); 356 log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName);
@@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
47 import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration; 47 import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration;
48 import org.thingsboard.server.common.data.id.DeviceId; 48 import org.thingsboard.server.common.data.id.DeviceId;
49 import org.thingsboard.server.common.data.rpc.RpcStatus; 49 import org.thingsboard.server.common.data.rpc.RpcStatus;
  50 +import org.thingsboard.server.common.data.yunteng.enums.ProtocolAnalysisEnum;
50 import org.thingsboard.server.common.data.yunteng.utils.ByteUtils; 51 import org.thingsboard.server.common.data.yunteng.utils.ByteUtils;
51 import org.thingsboard.server.common.msg.tools.TbRateLimitsException; 52 import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
52 import org.thingsboard.server.common.transport.SessionMsgListener; 53 import org.thingsboard.server.common.transport.SessionMsgListener;
@@ -175,7 +176,7 @@ public class UdpDatagramDataHandler @@ -175,7 +176,7 @@ public class UdpDatagramDataHandler
175 } 176 }
176 if (tcpMessage.getTelemetry()) { 177 if (tcpMessage.getTelemetry()) {
177 gatewaySessionHandler.onDeviceTelemetry( 178 gatewaySessionHandler.onDeviceTelemetry(
178 - devName, tcpMessage.getRequestId(), param.toString()); 179 + devName, tcpMessage.getRequestId(), param.toString(), ProtocolAnalysisEnum.CUSTOM);
179 } else { 180 } else {
180 // gatewaySessionHandler.onDeviceRpcResponse(devName, 181 // gatewaySessionHandler.onDeviceRpcResponse(devName,
181 // tcpMessage.getRequestId(), param.toString()); 182 // tcpMessage.getRequestId(), param.toString());
@@ -16,15 +16,11 @@ import org.thingsboard.server.common.data.yunteng.constant.QueryConstant; @@ -16,15 +16,11 @@ import org.thingsboard.server.common.data.yunteng.constant.QueryConstant;
16 import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; 16 import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException;
17 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; 17 import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
18 import org.thingsboard.server.common.data.yunteng.dto.*; 18 import org.thingsboard.server.common.data.yunteng.dto.*;
19 -import org.thingsboard.server.common.data.yunteng.enums.StatusEnum;  
20 -import org.thingsboard.server.common.data.yunteng.enums.TkModBusFunctionCode;  
21 -import org.thingsboard.server.common.data.yunteng.enums.TkScriptFunctionType;  
22 -import org.thingsboard.server.common.data.yunteng.enums.TransportTypeEnum; 19 +import org.thingsboard.server.common.data.yunteng.enums.*;
23 import org.thingsboard.server.common.data.yunteng.utils.ByteUtils; 20 import org.thingsboard.server.common.data.yunteng.utils.ByteUtils;
24 import org.thingsboard.server.common.data.yunteng.utils.CrcUtils; 21 import org.thingsboard.server.common.data.yunteng.utils.CrcUtils;
25 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; 22 import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData;
26 import org.thingsboard.server.dao.yunteng.entities.TkDeviceScriptEntity; 23 import org.thingsboard.server.dao.yunteng.entities.TkDeviceScriptEntity;
27 -import org.thingsboard.server.dao.yunteng.mapper.TkDeviceProfileMapper;  
28 import org.thingsboard.server.dao.yunteng.mapper.TkDeviceScriptMapper; 24 import org.thingsboard.server.dao.yunteng.mapper.TkDeviceScriptMapper;
29 import org.thingsboard.server.dao.yunteng.service.*; 25 import org.thingsboard.server.dao.yunteng.service.*;
30 26
@@ -39,7 +35,6 @@ public class TkDeviceScriptServiceImpl @@ -39,7 +35,6 @@ public class TkDeviceScriptServiceImpl
39 extends AbstractBaseService<TkDeviceScriptMapper, TkDeviceScriptEntity> 35 extends AbstractBaseService<TkDeviceScriptMapper, TkDeviceScriptEntity>
40 implements TkDeviceScriptService { 36 implements TkDeviceScriptService {
41 37
42 - private final TkDeviceProfileMapper profileMapper;  
43 private final TkCustomerDevice tkCustomerDevice; 38 private final TkCustomerDevice tkCustomerDevice;
44 private final TkDeviceProfileService tkDeviceProfileService; 39 private final TkDeviceProfileService tkDeviceProfileService;
45 private final TkDeviceService tkDeviceService; 40 private final TkDeviceService tkDeviceService;
@@ -322,8 +317,6 @@ public class TkDeviceScriptServiceImpl @@ -322,8 +317,6 @@ public class TkDeviceScriptServiceImpl
322 317
323 return result.toString(); 318 return result.toString();
324 } 319 }
325 -  
326 -  
327 private String modelHex(Integer max){ 320 private String modelHex(Integer max){
328 String addrStr = ByteUtils.integerToHex(max); 321 String addrStr = ByteUtils.integerToHex(max);
329 return addrStr.substring(addrStr.length()-4); 322 return addrStr.substring(addrStr.length()-4);
@@ -48,6 +48,7 @@ @@ -48,6 +48,7 @@
48 javaType="org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO"> 48 javaType="org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO">
49 <result property="name" column="profile_name"/> 49 <result property="name" column="profile_name"/>
50 <result property="transportType" column="transport_type"/> 50 <result property="transportType" column="transport_type"/>
  51 + <result property="profileData" column="profile_data" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/>
51 </association> 52 </association>
52 <association property="organizationDTO" 53 <association property="organizationDTO"
53 javaType="org.thingsboard.server.common.data.yunteng.dto.OrganizationDTO"> 54 javaType="org.thingsboard.server.common.data.yunteng.dto.OrganizationDTO">
@@ -83,7 +84,7 @@ @@ -83,7 +84,7 @@
83 </sql> 84 </sql>
84 <sql id="detailColumns"> 85 <sql id="detailColumns">
85 <include refid="pageColumns"/> 86 <include refid="pageColumns"/>
86 - ,idg.name gateway_name,idg.alias gateway_alias 87 + ,idg.name gateway_name,idg.alias gateway_alias,ifdp.profile_data
87 </sql> 88 </sql>
88 <sql id="pageColumns"> 89 <sql id="pageColumns">
89 <include refid="basicColumns"/> 90 <include refid="basicColumns"/>