20260611:平台对接bug修复(接口写死问题,轴力计无法推送数据)

This commit is contained in:
zhugongping 2026-06-11 21:53:45 +08:00
parent 972ebb36a9
commit bcc0dcf37c
9 changed files with 168 additions and 63 deletions

View File

@ -13,7 +13,10 @@
"Bash(python3 -c \"import fitz; print\\(''fitz ok''\\)\")",
"Bash(python3:*)",
"Bash(grep -A 5 \"INSERT INTO \\\\`sys_menu\\\\`\" /Users/admin/IdeaProjects/fastbee/sql/yack.sql)",
"Bash(mvn compile:*)"
"Bash(mvn compile:*)",
"Bash(mvn test:*)",
"Bash(mysql -hlocalhost -uroot -pfastbee fastbee -e \"SELECT id, serial_number, platform_id, device_type, enable FROM platform_device_mapping LIMIT 5;\")",
"Bash(mvn test-compile:*)"
]
}
}

View File

@ -3,19 +3,26 @@ import time
from binascii import unhexlify
# 目标服务器地址和端口
TARGET_HOST = 'localhost' # 请根据实际情况修改
TARGET_HOST = 'localhost' # 请根据实际情况修改
TARGET_PORT = 8888 # 请根据实际情况修改
# 你的注册指令数据
REGISTER_DATA_HEX = "7e80D2DA725711FB7e"
# 元数组 modbus 指令
MODBUS_DATA_HEX = (
"7e80D55746B29F687e",
"010310c07ffffe45217c3d41dfe9964140686641f4"
)
# "01031c4268442f41d30000418b6685000000004026f4cac00d1fd3c2ad2a9d6311",
# "02031c42488fd041c5800041865ab7000000003f364458c03f765ec2add9722da5",
# "03031c42ffdad541c78000421529b8000000003d5f38f94082080dc2abdf4f1f94"
# 01 03 1c 4268 442f 41d3 0000 418b 6685 0000 0000 4026 f4ca c00d 1fd3 c2ad 2a9d 6311
# 从机地址 功能码 字节长度 2个字节 CRC校验
# 在计算机中一个字节1Byte=8bit 而16进制表示法中,刚好 2位数表示一个字节 2^4=16
# Modbus 协议传输数据的基本单位是 16位的寄存器(2个字节)。
# 但是,像温度、压力这种带有小数点的数值,用 16 位(2个字节)来表示精度不够。因此,工业上通常会把 2 个连续的寄存器(即 4 个字节,32 bit) 捆绑在一起,用来表示一个 32位浮点数(Float32)
# 你的Modbus指令数据 01034040fb6f6141bd5555bfc82e00bd7d2000c2b0e1063e90c9983c9df308be91110041d9e3753fedf7df44789610c1d60000bf555555c477900041e1058342e1b9874f50
MODBUS_DATA_HEX = "01034040fb6f6141bd5555bfc82e00bd7d2000c2b0e1063e90c9983c9df308be91110041d9e3753fedf7df44789610c1d60000bf555555c477900041e1058342e1b9874f50"
def main():
# 将十六进制字符串转换为字节
register_bytes = unhexlify(REGISTER_DATA_HEX)
modbus_bytes = unhexlify(MODBUS_DATA_HEX)
try:
# 创建TCP套接字
@ -26,19 +33,19 @@ def main():
sock.connect((TARGET_HOST, TARGET_PORT))
print("已连接。")
# 1. 发送注册包
print("发送注册包...")
sock.sendall(register_bytes)
print("注册包已发送。")
for hex_bytes in MODBUS_DATA_HEX:
# 2. 等待0.5秒
print("等待 0.5 秒...")
time.sleep(0.5)
# 0.将十六进制字符串转换为字节
modbus_bytes = unhexlify(hex_bytes)
# 3. 发送Modbus数据包
print("发送Modbus数据包...")
sock.sendall(modbus_bytes)
print("Modbus数据包已发送。")
# 1. 发送注册包
print("发送注册包...")
sock.sendall(modbus_bytes)
print("注册包已发送。")
# 2. 等待0.5秒
print("等待 0.1 秒...")
time.sleep(0.5)
# (可选)接收最终响应
try:

View File

@ -24,8 +24,8 @@ public enum ModbusDataType {
FLOAT_CDAB("float-CDAB", "32位 浮点数(CDAB)"),
BIT("bit", "位");
String type;
String msg;
final String type;
final String msg;
public static ModbusDataType convert(String type) {
for (ModbusDataType value : ModbusDataType.values()) {

View File

@ -343,13 +343,19 @@ public class CalculateUtils {
/**
* 转32位浮点数 CDAB
*
* 转32位浮点数 CDAB模式 (寄存器交换,字节不交换)
* 例如: 传输顺序 [Reg1: 0x4268][Reg2: 0x442F] -> 实际内存 [0x442F4268]
* @param bytes
* @return
*/
public static Float toFloat32_DCBA(byte[] bytes) {
int intValue = ((bytes[3] & 0xFF) << 24) | ((bytes[2] & 0xFF) << 16) | ((bytes[1] & 0xFF) << 8)
| ((bytes[0] & 0xFF));
public static float toFloat32_CDAB(byte[] bytes) {
if (bytes.length < 4) throw new IllegalArgumentException("字节数组长度不足4");
// 假设 bytes[0-1] 是第一个寄存器,bytes[2-3] 是第二个寄存器
// CDAB: 将第二个寄存器放到高16位,第一个寄存器放到低16位
int intValue = ((bytes[2] & 0xFF) << 24) |
((bytes[3] & 0xFF) << 16) |
((bytes[0] & 0xFF) << 8) |
((bytes[1] & 0xFF));
return Float.intBitsToFloat(intValue);
}
@ -386,8 +392,8 @@ public class CalculateUtils {
case FLOAT_ABCD:
value = CalculateUtils.toFloat32_ABCD(bytes) + "";
break;
case FLOAT_CDAB: // todo:待改枚举页面枚举
value = CalculateUtils.toFloat32_DCBA(bytes) + "";
case FLOAT_CDAB:
value = CalculateUtils.toFloat32_CDAB(bytes) + "";
break;
}
}
@ -478,6 +484,8 @@ public class CalculateUtils {
byte[] bytes2 = intToBytes2(val1);
int i1 = bytesToInt2(bytes2);
System.out.println(i1);*/
System.out.println("parseValue = " + parseValue("float-ABCD", "4268442f"));
}
}

View File

@ -141,7 +141,7 @@ public class CRC16Utils {
byte[] byte8 = ByteUtils.hexToByte(crc8);
int b = CRC8(byte8);
System.out.println(b);
float v = CalculateUtils.toFloat32_DCBA(bytes);
float v = CalculateUtils.toFloat32_CDAB(bytes);
System.out.println(v);
}

View File

@ -34,7 +34,6 @@ import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -140,6 +139,14 @@ public class DcpPlatformAdapter {
return;
}
// 无数组字段 → 简单推送,使用子设备 platformDeviceId 和主表 fieldMapping
if (!hasArrayField(requestSchema)) {
// 如果只有一个设备完全可以走
handleSubDeviceSimplePush(platform, mapping, subMapping, reportSerialNumber,
payload, pushPath, interfaceCode, requestSchema);
return;
}
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping());
String baseUrl = platform.getBaseUrl() + pushPath;
@ -220,6 +227,22 @@ public class DcpPlatformAdapter {
}
}
/**
* 请求 schema 是否包含数组字段
*/
private boolean hasArrayField(JSONArray requestSchema) {
if (requestSchema == null || requestSchema.isEmpty()) {
return false;
}
for (int i = 0; i < requestSchema.size(); i++) {
JSONObject field = requestSchema.getJSONObject(i);
if ("array".equalsIgnoreCase(field.getString("type"))) {
return true;
}
}
return false;
}
/**
* 从子映射的 onboard_config 解析基准点序列号
*/
@ -266,12 +289,12 @@ public class DcpPlatformAdapter {
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping());
if (requestSchema == null || requestSchema.isEmpty()) {
return buildLegacyPushBody(mapping, fieldMappings, reportData);
return buildLegacyPushBody(mapping.getPlatformDeviceId(), fieldMappings, reportData);
}
JSONObject dcpData = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings);
if (dcpData.isEmpty() || !hasPayloadDrivenValue(dcpData, requestSchema)) {
return buildLegacyPushBody(mapping, fieldMappings, reportData);
if (dcpData.isEmpty() || hasPayloadDrivenValue(dcpData, requestSchema)) {
return buildLegacyPushBody(mapping.getPlatformDeviceId(), fieldMappings, reportData);
}
JSONArray arr = new JSONArray();
@ -279,13 +302,13 @@ public class DcpPlatformAdapter {
return arr;
}
private Object buildLegacyPushBody(PlatformDeviceMapping mapping, JSONArray fieldMappings, JSONObject reportData) {
private Object buildLegacyPushBody(String platformDeviceId, JSONArray fieldMappings, JSONObject reportData) {
if (fieldMappings == null || fieldMappings.isEmpty()) {
return null;
}
JSONObject dcpData = new JSONObject();
dcpData.put("deviceId", mapping.getPlatformDeviceId());
dcpData.put("deviceId", platformDeviceId);
dcpData.put("epoch", nowIsoTimestamp());
for (int i = 0; i < fieldMappings.size(); i++) {
@ -310,6 +333,64 @@ public class DcpPlatformAdapter {
return arr;
}
/**
* 网关子设备简单推送(推送接口无数组字段时走此路径)
* <p>
* 使用子设备 platformDeviceId 和主表 fieldMapping 组装请求体
*/
private void handleSubDeviceSimplePush(PlatformInstance platform, PlatformDeviceMapping mapping,
PlatformDeviceSubMapping subMapping,
String reportSerialNumber, String payload,
String pushPath, String interfaceCode,
JSONArray requestSchema) {
Object body = buildSubDevicePushBody(platform, mapping, subMapping, payload, requestSchema);
if (body == null) {
log.warn("[DCP] 网关子设备 {} 字段映射结果为空,跳过推送", reportSerialNumber);
return;
}
PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode, reportSerialNumber);
doHttpPost(platform, platform.getBaseUrl() + pushPath, JSON.toJSONString(body), logEntry);
pushLogService.asyncSave(logEntry);
}
/**
* 构建网关子设备的简单请求体
* <p>
* 复用主表 fieldMapping 进行物模型字段映射,deviceId 取自子映射行
*/
private Object buildSubDevicePushBody(PlatformInstance platform, PlatformDeviceMapping mapping,
PlatformDeviceSubMapping subMapping, String payload,
JSONArray requestSchema) {
JSONObject reportData = parsePayloadToMap(payload);
if (reportData == null || reportData.isEmpty()) {
return null;
}
// 平台设备ID
String platformDeviceId = StringUtils.hasText(subMapping.getPlatformDeviceId())
? subMapping.getPlatformDeviceId() : mapping.getPlatformDeviceId();
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping());
if (requestSchema == null || requestSchema.isEmpty()) {
return buildLegacyPushBody(platformDeviceId, fieldMappings, reportData);
}
JSONObject dcpData = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings);
if (dcpData.isEmpty() || hasPayloadDrivenValue(dcpData, requestSchema)) {
return buildLegacyPushBody(platformDeviceId, fieldMappings, reportData);
}
// 覆写 deviceId 为子设备的平台 ID
if (StringUtils.hasText(subMapping.getPlatformDeviceId())) {
dcpData.put("deviceId", subMapping.getPlatformDeviceId());
}
JSONArray arr = new JSONArray();
arr.add(dcpData);
return arr;
}
// ==================== 构建辅助 ====================
private JSONArray buildArrayPushBody(PlatformDeviceMapping mapping, JSONObject bodyFields,
@ -505,7 +586,7 @@ public class DcpPlatformAdapter {
private boolean hasPayloadDrivenValue(JSONObject body, JSONArray requestSchema) {
if (body == null || body.isEmpty() || requestSchema == null || requestSchema.isEmpty()) {
return false;
return true;
}
for (int i = 0; i < requestSchema.size(); i++) {
JSONObject schemaField = requestSchema.getJSONObject(i);
@ -514,10 +595,10 @@ public class DcpPlatformAdapter {
}
if (usesPayload(schemaField.getString("source"))
&& body.containsKey(schemaField.getString("name"))) {
return true;
return false;
}
}
return false;
return true;
}
private boolean usesPayload(String source) {
@ -681,8 +762,6 @@ public class DcpPlatformAdapter {
// ==================== HTTP 执行 ====================
// ==================== HTTP 执行 ====================
private void doHttpPost(PlatformInstance platform, String url, String bodyStr, PlatformPushLog logEntry) {
long startMs = System.currentTimeMillis();
logEntry.setRequestUrl(url);
@ -717,10 +796,9 @@ public class DcpPlatformAdapter {
}
}
private PlatformPushLog createLogEntry(PlatformInstance platform, PlatformDeviceMapping mapping, String ifaceCode) {
return createLogEntry(platform, mapping, ifaceCode, mapping.getSerialNumber());
}
/**
* 创建记录日志
*/
private PlatformPushLog createLogEntry(PlatformInstance platform, PlatformDeviceMapping mapping,
String ifaceCode, String serialNumber) {
PlatformPushLog logEntry = new PlatformPushLog();

View File

@ -1,6 +1,5 @@
package com.fastbee.iot.platform.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fastbee.iot.platform.domain.PlatformDeviceSubMapping;
import com.fastbee.iot.platform.mapper.PlatformDeviceSubMappingMapper;

View File

@ -129,7 +129,6 @@ public class DeviceServiceImpl implements IDeviceService {
@Resource
private IPAddressService ipAddressService;
@Resource
private EventLogMapper eventLogMapper;
@Resource
@ -464,6 +463,7 @@ public class DeviceServiceImpl implements IDeviceService {
// 删除网关绑定子设备关系
if (DeviceType.GATEWAY.getCode() == device.getDeviceType()) {
subGatewayMapper.deleteGatewayByGwDeviceId(deviceId);
// todo 需要删子设备
}
// 删除modbus轮询指令
String keyModbus = RedisKeyBuilder.buildModbusRuntimeCacheKey(device.getSerialNumber());

View File

@ -155,7 +155,7 @@
<div v-if="form.deviceType === 1" class="array-fields-section">
<div class="section-label">子设备映射</div>
<div class="schema-tip" style="margin-bottom:8px">
配置网关下参与上报的子设备,指定基准点和监测点角色。入网时每个监测点独立创建测点和绑定。
配置网关下参与上报的子设备,指定基准点(不会单独推送,与基准点一起推送)和监测点角色。入网时每个监测点独立创建测点和绑定。
</div>
<el-table :data="subMappingRows" size="mini" border style="width:100%">
<el-table-column label="子设备编号" min-width="160">
@ -431,29 +431,35 @@ export default {
})
},
onPushInterfaceChange(interfaceId) {
// 从推送接口的 requestSchema 提取字段作为数据映射模板
// 从推送接口的 requestSchema 提取字段作为数据映射模板,切换接口时全量刷新
if (!interfaceId) return
const iface = this.interfaceOptions.find(x => x.id === interfaceId)
if (!iface || !iface.requestSchema) return
try {
const schema = JSON.parse(iface.requestSchema)
const oldFields = this.fieldMappingRows || []
const oldItemFields = this.itemFieldMappingRows || []
// 网关设备:提取 array 类型字段的 itemSchema 作为数组项映射模板
const arrayField = schema.find(f => f.type === 'array' && f.itemSchema && f.itemSchema.length > 0)
if (arrayField && this.form.deviceType === 1) {
const identifierFields = (arrayField.itemSchema || []).filter(s => s.source === 'thingsModel' || s.source === 'identifier')
if (identifierFields.length > 0 && this.itemFieldMappingRows.length === 0) {
this.itemFieldMappingRows = identifierFields.map(s => ({ platformField: s.name, identifier: '' }))
}
}
// 标量字段映射(body 层):所有设备类型都提取,同名平台字段保留已选标识
const scalarFields = schema.filter(f =>
f.type !== 'array' && (f.source === 'thingsModel' || f.source === 'identifier')
)
this.fieldMappingRows = scalarFields.map(s => ({
identifier: (oldFields.find(r => r.platformField === s.name) || {}).identifier || '',
platformField: s.name
}))
// 直连设备:提取标量字段(非 array)作为字段映射模板
if (this.form.deviceType !== 1) {
const scalarFields = schema.filter(f =>
f.type !== 'array' && (f.source === 'thingsModel' || f.source === 'identifier')
)
if (scalarFields.length > 0 && this.fieldMappingRows.length === 0) {
this.fieldMappingRows = scalarFields.map(s => ({ identifier: '', platformField: s.name }))
// 网关设备额外提取数组字段的 itemSchema
if (this.form.deviceType === 1) {
const arrayField = schema.find(f => f.type === 'array' && f.itemSchema && f.itemSchema.length > 0)
if (arrayField) {
const identifierFields = (arrayField.itemSchema || []).filter(s => s.source === 'thingsModel' || s.source === 'identifier')
this.itemFieldMappingRows = identifierFields.map(s => ({
platformField: s.name,
identifier: (oldItemFields.find(r => r.platformField === s.name) || {}).identifier || ''
}))
} else {
this.itemFieldMappingRows = []
}
}
} catch (e) { /* ignore */ }
@ -514,6 +520,10 @@ export default {
})
})
this.subDeviceOptions = Array.from(optionMap.values())
// 新增模式下,根据是否有子设备自动推断设备类型,减少人工选择出错
if (!this.form.id) {
this.form.deviceType = this.subDeviceOptions.length > 0 ? 1 : 0
}
// 网关设备且子映射为空时,自动预填所有子设备,第一个为基准点
if (this.form.deviceType === 1 && this.subMappingRows.length === 0 && this.subDeviceOptions.length > 0) {
this.subMappingRows = this.subDeviceOptions.map((device, idx) => {