Commit f7129f32adc84db5ad70426226466831d0fac36c
Merge branch 'master_dev_modbus' into 'master_dev'
feat: 增加标准modbus解析 See merge request yunteng/thingskit!334
Showing
25 changed files
with
1452 additions
and
118 deletions
... | ... | @@ -22,19 +22,18 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; |
22 | 22 | import org.thingsboard.server.common.data.yunteng.common.AddGroup; |
23 | 23 | import org.thingsboard.server.common.data.yunteng.common.DeleteGroup; |
24 | 24 | import org.thingsboard.server.common.data.yunteng.common.UpdateGroup; |
25 | +import org.thingsboard.server.common.data.yunteng.core.exception.ThingsKitException; | |
25 | 26 | import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; |
26 | 27 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
27 | 28 | import org.thingsboard.server.common.data.yunteng.dto.*; |
28 | 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 | 31 | import org.thingsboard.server.common.data.yunteng.utils.tools.ResponseResult; |
34 | 32 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; |
35 | 33 | import org.thingsboard.server.controller.BaseController; |
36 | 34 | import org.thingsboard.server.dao.yunteng.service.ThingsModelService; |
37 | 35 | import org.thingsboard.server.dao.yunteng.service.TkDeviceProfileService; |
36 | +import org.thingsboard.server.utils.ImportModbusUtils; | |
38 | 37 | |
39 | 38 | import java.io.File; |
40 | 39 | import java.io.IOException; |
... | ... | @@ -42,6 +41,7 @@ import java.io.InputStream; |
42 | 41 | import java.util.HashMap; |
43 | 42 | import java.util.List; |
44 | 43 | import java.util.Map; |
44 | +import java.util.regex.Pattern; | |
45 | 45 | |
46 | 46 | import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.*; |
47 | 47 | import static org.thingsboard.server.common.data.yunteng.constant.QueryConstant.ORDER_TYPE; |
... | ... | @@ -234,16 +234,221 @@ public class ThingsModelController extends BaseController { |
234 | 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 | 272 | @ApiOperation(value = "通过excel导入物模型") |
238 | 273 | @PreAuthorize("@check.checkPermissions({'SYS_ADMIN','PLATFORM_ADMIN','TENANT_ADMIN'},{'api:yt:things_model:excel_import'})") |
239 | 274 | @PostMapping("/csvImport") |
240 | - public ResponseResult<String> csvImport(String deviceProfileId, String categoryId, | |
275 | + public ResponseResult<String> csvImport(String deviceProfileId, String categoryId,String type, | |
241 | 276 | @RequestPart("file") MultipartFile file) throws Exception { |
242 | 277 | if(file.isEmpty()){ |
243 | - return null; | |
278 | + throw new ThingsKitException(ErrorMessage.INVALID_PARAMETER); | |
244 | 279 | } |
280 | + try{ | |
245 | 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 | 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 | 452 | int succeed = 0;//成功数量 |
248 | 453 | int failed =0;//失败数量 |
249 | 454 | StringBuffer failedString = new StringBuffer(); |
... | ... | @@ -282,8 +487,6 @@ public class ThingsModelController extends BaseController { |
282 | 487 | continue; |
283 | 488 | } |
284 | 489 | } |
285 | - | |
286 | - String tenantId = getCurrentUser().getCurrentTenantId(); | |
287 | 490 | Boolean identifierState = thingsModelService.getByIdentifier(tenantId,deviceProfileId,categoryId,identifier); |
288 | 491 | if(!identifierState){ |
289 | 492 | failed++; |
... | ... | @@ -349,32 +552,4 @@ public class ThingsModelController extends BaseController { |
349 | 552 | } |
350 | 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 | +} | ... | ... |
No preview for this file type
... | ... | @@ -3,6 +3,7 @@ package org.thingsboard.server.common.data.device.profile; |
3 | 3 | import lombok.Data; |
4 | 4 | import org.thingsboard.server.common.data.DeviceTransportType; |
5 | 5 | import org.thingsboard.server.common.data.validation.NoXss; |
6 | +import org.thingsboard.server.common.data.yunteng.enums.ProtocolAnalysisEnum; | |
6 | 7 | |
7 | 8 | @Data |
8 | 9 | public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { |
... | ... | @@ -26,6 +27,12 @@ public class TkTcpDeviceProfileTransportConfiguration implements DeviceProfileTr |
26 | 27 | @NoXss |
27 | 28 | private String downScriptId; |
28 | 29 | |
30 | + /** | |
31 | + * 协议: 默认自定义 | |
32 | + */ | |
33 | + @NoXss | |
34 | + private ProtocolAnalysisEnum protocol = ProtocolAnalysisEnum.CUSTOM; | |
35 | + | |
29 | 36 | @Override |
30 | 37 | public DeviceTransportType getType() { |
31 | 38 | return DeviceTransportType.TCP; | ... | ... |
... | ... | @@ -37,7 +37,36 @@ public interface FastIotConstants { |
37 | 37 | String ORGANIZATION = "organization"; |
38 | 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 | 70 | interface TBCacheConfig { |
42 | 71 | String TB_CACHE_CONFIG_KEY = "TB_CONNECT_CACHE"; |
43 | 72 | String EXISTING_TENANT = "EXISTING_TENANT"; | ... | ... |
... | ... | @@ -120,6 +120,7 @@ public enum ErrorMessage { |
120 | 120 | EZVIZ_API_ERROR(400095,"荧石视频获取TokenAPI调用失败【%s】,错误码【%s】"), |
121 | 121 | EZVIZ_GET_URL_ERROR(400096,"荧石API调用获取URL失败!!"), |
122 | 122 | IMPORT_TCP_ERROR(400097,"TCP产品不能导入INT,DOUBLE,BOOL,TEXT以外的数据类型属性!!"), |
123 | + IMPORT_ERROR(400098,"请使用模板excel重新导入"), | |
123 | 124 | HAVE_NO_PERMISSION(500002,"没有修改权限"), |
124 | 125 | NOT_ALLOED_ISOLATED_IN_MONOLITH(500003,"【monolith】模式下,不能选择【isolated】类型的租户配置"); |
125 | 126 | ... | ... |
... | ... | @@ -2,6 +2,7 @@ package org.thingsboard.server.common.data.yunteng.dto; |
2 | 2 | |
3 | 3 | import io.swagger.annotations.ApiModelProperty; |
4 | 4 | import lombok.Data; |
5 | +import org.thingsboard.server.common.data.yunteng.enums.HexByteOrderEnum; | |
5 | 6 | import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType; |
6 | 7 | import java.io.Serializable; |
7 | 8 | import java.util.List; |
... | ... | @@ -28,4 +29,7 @@ public class TkDeviceRpcDTO implements Serializable { |
28 | 29 | |
29 | 30 | @ApiModelProperty(value = "寄存器值") |
30 | 31 | private List<Integer> registerValues; |
32 | + | |
33 | + @ApiModelProperty(value = "顺序") | |
34 | + private HexByteOrderEnum hexByteOrderEnum; | |
31 | 35 | } | ... | ... |
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/enums/OperationTypeEnum.java
0 → 100644
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 | +} | ... | ... |
... | ... | @@ -3,40 +3,41 @@ package org.thingsboard.server.common.data.yunteng.utils; |
3 | 3 | import org.thingsboard.server.common.data.yunteng.enums.TkModBusCheckType; |
4 | 4 | |
5 | 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 | } | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/utils/HexConvertUtils.java
0 → 100644
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 | +} | ... | ... |
common/data/src/main/java/org/thingsboard/server/common/data/yunteng/utils/ModbusUtils.java
0 → 100644
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 | 23 | import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_OPEN; |
24 | 24 | |
25 | 25 | import com.fasterxml.jackson.databind.JsonNode; |
26 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
27 | +import com.fasterxml.jackson.databind.node.TextNode; | |
26 | 28 | import com.google.common.util.concurrent.FutureCallback; |
27 | 29 | import com.google.common.util.concurrent.Futures; |
28 | 30 | import com.google.common.util.concurrent.ListenableFuture; |
... | ... | @@ -34,23 +36,17 @@ import io.netty.channel.ChannelHandlerContext; |
34 | 36 | import io.netty.channel.ChannelInboundHandlerAdapter; |
35 | 37 | import io.netty.handler.codec.mqtt.*; |
36 | 38 | import io.netty.handler.ssl.SslHandler; |
37 | -import io.netty.util.CharsetUtil; | |
38 | 39 | import io.netty.util.ReferenceCountUtil; |
39 | 40 | import io.netty.util.concurrent.Future; |
40 | 41 | import io.netty.util.concurrent.GenericFutureListener; |
41 | 42 | import java.io.IOException; |
42 | 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 | 45 | import java.util.concurrent.atomic.AtomicInteger; |
48 | -import java.util.regex.Pattern; | |
46 | + | |
49 | 47 | import lombok.extern.slf4j.Slf4j; |
50 | 48 | import org.apache.commons.lang3.StringUtils; |
51 | 49 | import org.checkerframework.checker.nullness.qual.Nullable; |
52 | -import org.springframework.boot.context.event.ApplicationReadyEvent; | |
53 | -import org.springframework.context.event.EventListener; | |
54 | 50 | import org.thingsboard.common.util.JacksonUtil; |
55 | 51 | import org.thingsboard.server.common.data.DataConstants; |
56 | 52 | import org.thingsboard.server.common.data.Device; |
... | ... | @@ -59,9 +55,11 @@ import org.thingsboard.server.common.data.DeviceTransportType; |
59 | 55 | import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration; |
60 | 56 | import org.thingsboard.server.common.data.id.DeviceId; |
61 | 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 | 63 | import org.thingsboard.server.common.msg.tools.TbRateLimitsException; |
66 | 64 | import org.thingsboard.server.common.transport.SessionMsgListener; |
67 | 65 | import org.thingsboard.server.common.transport.TransportService; |
... | ... | @@ -199,15 +197,72 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements |
199 | 197 | if (!checkConnected(ctx, tcpMessage)) { |
200 | 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 | 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 | 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 | 280 | log.trace("[{}][{}] Processing publish msg [{}]!", sessionId, deviceSessionCtx.getDeviceId(), tcpMessage); |
227 | 281 | try { |
228 | 282 | Map<String, Object> datas = tcpMessage.getDatas(); |
... | ... | @@ -232,7 +286,8 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements |
232 | 286 | return; |
233 | 287 | } |
234 | 288 | if (tcpMessage.getTelemetry()) { |
235 | - gatewaySessionHandler.onDeviceTelemetry(devName, tcpMessage.getRequestId(), param.toString()); | |
289 | + gatewaySessionHandler.onDeviceTelemetry(devName, tcpMessage.getRequestId(), param.toString(), | |
290 | + ProtocolAnalysisEnum.CUSTOM); | |
236 | 291 | } else { |
237 | 292 | // gatewaySessionHandler.onDeviceRpcResponse(devName, tcpMessage.getRequestId(), param.toString()); |
238 | 293 | } |
... | ... | @@ -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 | 307 | log.trace("[{}][{}] Processing publish msg [{}]!", sessionId, deviceSessionCtx.getDeviceId(), tcpMessage); |
253 | 308 | try { |
254 | 309 | TcpTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); |
... | ... | @@ -435,7 +490,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements |
435 | 490 | } else { |
436 | 491 | DeviceProfile profile = msg.getDeviceProfile(); |
437 | 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 | 494 | authedCounter.incrementAndGet(); |
440 | 495 | return; |
441 | 496 | } |
... | ... | @@ -562,6 +617,7 @@ public class TcpTransportHandler extends ChannelInboundHandlerAdapter implements |
562 | 617 | @Override |
563 | 618 | public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt) { |
564 | 619 | deviceSessionCtx.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); |
620 | + deviceSessionCtx.setDeviceCode(JacksonUtil.toString(device.getAdditionalInfo())); | |
565 | 621 | } |
566 | 622 | |
567 | 623 | @Override | ... | ... |
common/transport/tcp/src/main/java/org/thingsboard/server/transport/tcp/adaptors/JsonTcpAdaptor.java
... | ... | @@ -15,9 +15,9 @@ |
15 | 15 | */ |
16 | 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 | 21 | import com.google.gson.JsonElement; |
22 | 22 | import com.google.gson.JsonObject; |
23 | 23 | import com.google.gson.JsonParser; |
... | ... | @@ -29,6 +29,11 @@ import org.springframework.stereotype.Component; |
29 | 29 | import org.springframework.util.StringUtils; |
30 | 30 | import org.thingsboard.common.util.JacksonUtil; |
31 | 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 | 37 | import org.thingsboard.server.common.transport.adaptor.AdaptorException; |
33 | 38 | import org.thingsboard.server.common.transport.adaptor.JsonConverter; |
34 | 39 | import org.thingsboard.server.gen.transport.TransportProtos; |
... | ... | @@ -38,6 +43,8 @@ import java.nio.charset.Charset; |
38 | 43 | import java.nio.charset.StandardCharsets; |
39 | 44 | import java.util.*; |
40 | 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 | 57 | protected static final Charset UTF8 = StandardCharsets.UTF_8; |
51 | 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 | 64 | @Override |
55 | 65 | public TransportProtos.PostTelemetryMsg convertToPostTelemetry(TcpDeviceWareSessionContext ctx, String payload) throws AdaptorException { |
... | ... | @@ -164,6 +174,352 @@ public class JsonTcpAdaptor implements TcpTransportAdaptor { |
164 | 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 | 523 | public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { |
168 | 524 | String payload = validatePayload(sessionId, payloadData, false); |
169 | 525 | try { |
... | ... | @@ -267,6 +623,4 @@ public class JsonTcpAdaptor implements TcpTransportAdaptor { |
267 | 623 | // return new MqttPublishMessage(mqttFixedHeader, header, payload); |
268 | 624 | return null; |
269 | 625 | } |
270 | - | |
271 | - | |
272 | 626 | } | ... | ... |
... | ... | @@ -78,6 +78,8 @@ public interface TcpTransportAdaptor { |
78 | 78 | |
79 | 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 | 83 | public static byte[] toBytes(ByteBuf inbound) { |
82 | 84 | byte[] bytes = new byte[inbound.readableBytes()]; |
83 | 85 | int readerIndex = inbound.readerIndex(); | ... | ... |
1 | 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 | 6 | import com.google.common.util.concurrent.FutureCallback; |
4 | 7 | import com.google.common.util.concurrent.Futures; |
5 | 8 | import com.google.common.util.concurrent.ListenableFuture; |
6 | 9 | import com.google.common.util.concurrent.MoreExecutors; |
7 | 10 | import lombok.Getter; |
11 | +import lombok.Setter; | |
8 | 12 | import lombok.extern.slf4j.Slf4j; |
9 | 13 | import org.apache.commons.lang3.StringUtils; |
10 | 14 | import org.jetbrains.annotations.Nullable; |
... | ... | @@ -12,6 +16,8 @@ import org.thingsboard.common.util.JacksonUtil; |
12 | 16 | import org.thingsboard.server.common.data.DeviceProfile; |
13 | 17 | import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; |
14 | 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 | 21 | import org.thingsboard.server.common.data.yunteng.enums.TkScriptFunctionType; |
16 | 22 | import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; |
17 | 23 | import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; |
... | ... | @@ -40,7 +46,14 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont |
40 | 46 | private volatile UUID telemetryScriptId; |
41 | 47 | @Getter |
42 | 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 | 58 | * 设备唯一标识符,例如:设备SN、设备地址码等。数据内携带标识符 |
46 | 59 | */ |
... | ... | @@ -65,6 +78,7 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont |
65 | 78 | public void setDeviceInfo(TransportDeviceInfo deviceInfo) { |
66 | 79 | super.setDeviceInfo(deviceInfo); |
67 | 80 | deviceName = deviceInfo.getDeviceName(); |
81 | + setDeviceCode(deviceInfo.getAdditionalInfo()); | |
68 | 82 | } |
69 | 83 | |
70 | 84 | @Override |
... | ... | @@ -86,10 +100,25 @@ public abstract class TcpDeviceWareSessionContext extends DeviceAwareSessionCont |
86 | 100 | if(null != tcpConfiguration && StringUtils.isNotEmpty(tcpConfiguration.getAuthScriptId())){ |
87 | 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 | 33 | import org.springframework.util.StringUtils; |
34 | 34 | import org.thingsboard.common.util.JacksonUtil; |
35 | 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 | 38 | import org.thingsboard.server.common.data.yunteng.utils.ByteUtils; |
37 | 39 | import org.thingsboard.server.common.transport.TransportService; |
38 | 40 | import org.thingsboard.server.common.transport.TransportServiceCallback; |
... | ... | @@ -44,6 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro |
44 | 46 | import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; |
45 | 47 | import org.thingsboard.server.transport.tcp.TcpTransportContext; |
46 | 48 | import org.thingsboard.server.transport.tcp.adaptors.TcpTransportAdaptor; |
49 | +import org.thingsboard.server.transport.tcp.adaptors.TcpUpEntry; | |
47 | 50 | import org.thingsboard.server.transport.tcp.util.ByteBufUtils; |
48 | 51 | |
49 | 52 | import javax.annotation.Nullable; |
... | ... | @@ -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 | 101 | Futures.addCallback(checkDeviceConnected(deviceName), |
99 | 102 | new FutureCallback<>() { |
100 | 103 | @Override |
101 | 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 | 120 | @Override |
... | ... | @@ -116,6 +124,16 @@ public class TcpGatewaySessionHandler { |
116 | 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 | 138 | public void onGatewayDisconnect() { |
121 | 139 | devices.forEach(this::deregisterSession); |
... | ... | @@ -332,6 +350,7 @@ public class TcpGatewaySessionHandler { |
332 | 350 | |
333 | 351 | |
334 | 352 | private void deregisterSession(String deviceName, TcpGatewayDeviceSessionCtx deviceSessionCtx) { |
353 | + devices.remove(deviceName); | |
335 | 354 | transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); |
336 | 355 | transportService.process(deviceSessionCtx.getSessionInfo(), SESSION_EVENT_MSG_CLOSED, null); |
337 | 356 | log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName); | ... | ... |
... | ... | @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; |
47 | 47 | import org.thingsboard.server.common.data.device.profile.TkTcpDeviceProfileTransportConfiguration; |
48 | 48 | import org.thingsboard.server.common.data.id.DeviceId; |
49 | 49 | import org.thingsboard.server.common.data.rpc.RpcStatus; |
50 | +import org.thingsboard.server.common.data.yunteng.enums.ProtocolAnalysisEnum; | |
50 | 51 | import org.thingsboard.server.common.data.yunteng.utils.ByteUtils; |
51 | 52 | import org.thingsboard.server.common.msg.tools.TbRateLimitsException; |
52 | 53 | import org.thingsboard.server.common.transport.SessionMsgListener; |
... | ... | @@ -175,7 +176,7 @@ public class UdpDatagramDataHandler |
175 | 176 | } |
176 | 177 | if (tcpMessage.getTelemetry()) { |
177 | 178 | gatewaySessionHandler.onDeviceTelemetry( |
178 | - devName, tcpMessage.getRequestId(), param.toString()); | |
179 | + devName, tcpMessage.getRequestId(), param.toString(), ProtocolAnalysisEnum.CUSTOM); | |
179 | 180 | } else { |
180 | 181 | // gatewaySessionHandler.onDeviceRpcResponse(devName, |
181 | 182 | // tcpMessage.getRequestId(), param.toString()); | ... | ... |
... | ... | @@ -16,15 +16,11 @@ import org.thingsboard.server.common.data.yunteng.constant.QueryConstant; |
16 | 16 | import org.thingsboard.server.common.data.yunteng.core.exception.TkDataValidationException; |
17 | 17 | import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage; |
18 | 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 | 20 | import org.thingsboard.server.common.data.yunteng.utils.ByteUtils; |
24 | 21 | import org.thingsboard.server.common.data.yunteng.utils.CrcUtils; |
25 | 22 | import org.thingsboard.server.common.data.yunteng.utils.tools.TkPageData; |
26 | 23 | import org.thingsboard.server.dao.yunteng.entities.TkDeviceScriptEntity; |
27 | -import org.thingsboard.server.dao.yunteng.mapper.TkDeviceProfileMapper; | |
28 | 24 | import org.thingsboard.server.dao.yunteng.mapper.TkDeviceScriptMapper; |
29 | 25 | import org.thingsboard.server.dao.yunteng.service.*; |
30 | 26 | |
... | ... | @@ -39,7 +35,6 @@ public class TkDeviceScriptServiceImpl |
39 | 35 | extends AbstractBaseService<TkDeviceScriptMapper, TkDeviceScriptEntity> |
40 | 36 | implements TkDeviceScriptService { |
41 | 37 | |
42 | - private final TkDeviceProfileMapper profileMapper; | |
43 | 38 | private final TkCustomerDevice tkCustomerDevice; |
44 | 39 | private final TkDeviceProfileService tkDeviceProfileService; |
45 | 40 | private final TkDeviceService tkDeviceService; |
... | ... | @@ -322,8 +317,6 @@ public class TkDeviceScriptServiceImpl |
322 | 317 | |
323 | 318 | return result.toString(); |
324 | 319 | } |
325 | - | |
326 | - | |
327 | 320 | private String modelHex(Integer max){ |
328 | 321 | String addrStr = ByteUtils.integerToHex(max); |
329 | 322 | return addrStr.substring(addrStr.length()-4); | ... | ... |
... | ... | @@ -48,6 +48,7 @@ |
48 | 48 | javaType="org.thingsboard.server.common.data.yunteng.dto.DeviceProfileDTO"> |
49 | 49 | <result property="name" column="profile_name"/> |
50 | 50 | <result property="transportType" column="transport_type"/> |
51 | + <result property="profileData" column="profile_data" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler"/> | |
51 | 52 | </association> |
52 | 53 | <association property="organizationDTO" |
53 | 54 | javaType="org.thingsboard.server.common.data.yunteng.dto.OrganizationDTO"> |
... | ... | @@ -83,7 +84,7 @@ |
83 | 84 | </sql> |
84 | 85 | <sql id="detailColumns"> |
85 | 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 | 88 | </sql> |
88 | 89 | <sql id="pageColumns"> |
89 | 90 | <include refid="basicColumns"/> | ... | ... |