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 -c \"import fitz; print\\(''fitz ok''\\)\")",
"Bash(python3:*)", "Bash(python3:*)",
"Bash(grep -A 5 \"INSERT INTO \\\\`sys_menu\\\\`\" /Users/admin/IdeaProjects/fastbee/sql/yack.sql)", "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 from binascii import unhexlify
# 目标服务器地址和端口 # 目标服务器地址和端口
TARGET_HOST = 'localhost' # 请根据实际情况修改 TARGET_HOST = 'localhost' # 请根据实际情况修改
TARGET_PORT = 8888 # 请根据实际情况修改 TARGET_PORT = 8888 # 请根据实际情况修改
# 你的注册指令数据 # 元数组 modbus 指令
REGISTER_DATA_HEX = "7e80D2DA725711FB7e" 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(): def main():
# 将十六进制字符串转换为字节
register_bytes = unhexlify(REGISTER_DATA_HEX)
modbus_bytes = unhexlify(MODBUS_DATA_HEX)
try: try:
# 创建TCP套接字 # 创建TCP套接字
@ -26,19 +33,19 @@ def main():
sock.connect((TARGET_HOST, TARGET_PORT)) sock.connect((TARGET_HOST, TARGET_PORT))
print("已连接。") print("已连接。")
# 1. 发送注册包 for hex_bytes in MODBUS_DATA_HEX:
print("发送注册包...")
sock.sendall(register_bytes)
print("注册包已发送。")
# 2. 等待0.5秒 # 0.将十六进制字符串转换为字节
print("等待 0.5 秒...") modbus_bytes = unhexlify(hex_bytes)
time.sleep(0.5)
# 3. 发送Modbus数据包 # 1. 发送注册包
print("发送Modbus数据包...") print("发送注册包...")
sock.sendall(modbus_bytes) sock.sendall(modbus_bytes)
print("Modbus数据包已发送。") print("注册包已发送。")
# 2. 等待0.5秒
print("等待 0.1 秒...")
time.sleep(0.5)
# (可选)接收最终响应 # (可选)接收最终响应
try: try:

View File

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

View File

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

View File

@ -34,7 +34,6 @@ import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException; import java.time.format.DateTimeParseException;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -140,6 +139,14 @@ public class DcpPlatformAdapter {
return; return;
} }
// 无数组字段 → 简单推送,使用子设备 platformDeviceId 和主表 fieldMapping
if (!hasArrayField(requestSchema)) {
// 如果只有一个设备完全可以走
handleSubDeviceSimplePush(platform, mapping, subMapping, reportSerialNumber,
payload, pushPath, interfaceCode, requestSchema);
return;
}
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping());
String baseUrl = platform.getBaseUrl() + pushPath; 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 解析基准点序列号 * 从子映射的 onboard_config 解析基准点序列号
*/ */
@ -266,12 +289,12 @@ public class DcpPlatformAdapter {
JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping()); JSONArray fieldMappings = parseJsonArray(mapping.getFieldMapping());
if (requestSchema == null || requestSchema.isEmpty()) { if (requestSchema == null || requestSchema.isEmpty()) {
return buildLegacyPushBody(mapping, fieldMappings, reportData); return buildLegacyPushBody(mapping.getPlatformDeviceId(), fieldMappings, reportData);
} }
JSONObject dcpData = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings); JSONObject dcpData = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings);
if (dcpData.isEmpty() || !hasPayloadDrivenValue(dcpData, requestSchema)) { if (dcpData.isEmpty() || hasPayloadDrivenValue(dcpData, requestSchema)) {
return buildLegacyPushBody(mapping, fieldMappings, reportData); return buildLegacyPushBody(mapping.getPlatformDeviceId(), fieldMappings, reportData);
} }
JSONArray arr = new JSONArray(); JSONArray arr = new JSONArray();
@ -279,13 +302,13 @@ public class DcpPlatformAdapter {
return arr; 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()) { if (fieldMappings == null || fieldMappings.isEmpty()) {
return null; return null;
} }
JSONObject dcpData = new JSONObject(); JSONObject dcpData = new JSONObject();
dcpData.put("deviceId", mapping.getPlatformDeviceId()); dcpData.put("deviceId", platformDeviceId);
dcpData.put("epoch", nowIsoTimestamp()); dcpData.put("epoch", nowIsoTimestamp());
for (int i = 0; i < fieldMappings.size(); i++) { for (int i = 0; i < fieldMappings.size(); i++) {
@ -310,6 +333,64 @@ public class DcpPlatformAdapter {
return arr; 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, private JSONArray buildArrayPushBody(PlatformDeviceMapping mapping, JSONObject bodyFields,
@ -505,7 +586,7 @@ public class DcpPlatformAdapter {
private boolean hasPayloadDrivenValue(JSONObject body, JSONArray requestSchema) { private boolean hasPayloadDrivenValue(JSONObject body, JSONArray requestSchema) {
if (body == null || body.isEmpty() || requestSchema == null || requestSchema.isEmpty()) { if (body == null || body.isEmpty() || requestSchema == null || requestSchema.isEmpty()) {
return false; return true;
} }
for (int i = 0; i < requestSchema.size(); i++) { for (int i = 0; i < requestSchema.size(); i++) {
JSONObject schemaField = requestSchema.getJSONObject(i); JSONObject schemaField = requestSchema.getJSONObject(i);
@ -514,10 +595,10 @@ public class DcpPlatformAdapter {
} }
if (usesPayload(schemaField.getString("source")) if (usesPayload(schemaField.getString("source"))
&& body.containsKey(schemaField.getString("name"))) { && body.containsKey(schemaField.getString("name"))) {
return true; return false;
} }
} }
return false; return true;
} }
private boolean usesPayload(String source) { private boolean usesPayload(String source) {
@ -681,8 +762,6 @@ public class DcpPlatformAdapter {
// ==================== HTTP 执行 ==================== // ==================== HTTP 执行 ====================
// ==================== HTTP 执行 ====================
private void doHttpPost(PlatformInstance platform, String url, String bodyStr, PlatformPushLog logEntry) { private void doHttpPost(PlatformInstance platform, String url, String bodyStr, PlatformPushLog logEntry) {
long startMs = System.currentTimeMillis(); long startMs = System.currentTimeMillis();
logEntry.setRequestUrl(url); 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, private PlatformPushLog createLogEntry(PlatformInstance platform, PlatformDeviceMapping mapping,
String ifaceCode, String serialNumber) { String ifaceCode, String serialNumber) {
PlatformPushLog logEntry = new PlatformPushLog(); PlatformPushLog logEntry = new PlatformPushLog();

View File

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

View File

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

View File

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