diff --git a/src/api/iot/product/product/index.ts b/src/api/iot/product/product/index.ts index c9f273ebe02bfcacc5e2e74a22a00308a3b9207b..c2a047ca922a03e2f639e32dc2a98c43636dfcb2 100644 --- a/src/api/iot/product/product/index.ts +++ b/src/api/iot/product/product/index.ts @@ -16,6 +16,8 @@ export interface ProductVO { locationType: number // 设备类型 netType: number // 联网方式 codecType: string // 数据格式(编解码器类型) + protocolType?: string // 协议类型(STANDARD, MODBUS_RTU, MODBUS_TCP) + modbusSlaveId?: number // Modbus从站ID(1-247) deviceCount: number // 设备数量 createTime: Date // 创建时间 } diff --git a/src/api/iot/rule/data/sink/index.ts b/src/api/iot/rule/data/sink/index.ts index 3e2755e0de46f45e5014adc80b487f13894c61fc..5a70abc29d8b7623262f210028a5aa2b9cc507b3 100644 --- a/src/api/iot/rule/data/sink/index.ts +++ b/src/api/iot/rule/data/sink/index.ts @@ -11,6 +11,7 @@ export interface DataSinkVO { config?: | HttpConfig | MqttConfig + | DatabaseConfig | RocketMQConfig | KafkaMQConfig | RabbitMQConfig @@ -79,6 +80,17 @@ export interface RedisStreamMQConfig extends Config { topic: string } +/** Database 配置 */ +export interface DatabaseConfig extends Config { + url: string // 数据库连接 URL + username: string // 数据库用户名 + password: string // 数据库密码 + driverClassName: string // 数据库驱动 + tableName: string // 目标表名 + fieldMapping: Record // 字段映射 + saveMetadata: boolean // 是否保存元数据 +} + /** 数据流转目的类型 */ export const IotDataSinkTypeEnum = { HTTP: 1, @@ -122,5 +134,31 @@ export const DataSinkApi = { // 查询数据流转目的(精简)列表 getDataSinkSimpleList() { return request.get({ url: '/iot/data-sink/simple-list' }) + }, + + // 自动创建数据库表 + autoCreateTable: async (data: { + url: string + username: string + password: string + driverClassName: string + tableName: string + fieldMapping: Record + saveMetadata: boolean + }) => { + return await request.post({ url: `/iot/data-sink/database/auto-create-table`, data }) + }, + + // 同步数据库表结构 + syncTableStructure: async (data: { + url: string + username: string + password: string + driverClassName: string + tableName: string + fieldMapping: Record + saveMetadata: boolean + }) => { + return await request.post({ url: `/iot/data-sink/database/sync-table-structure`, data }) } } diff --git a/src/api/iot/thingmodel/index.ts b/src/api/iot/thingmodel/index.ts index bcf9e0707bab21a13302ce2c0daee5e634378a05..3b93c38c13e993ff927e76211448e761f4acccf6 100644 --- a/src/api/iot/thingmodel/index.ts +++ b/src/api/iot/thingmodel/index.ts @@ -78,6 +78,18 @@ export interface ThingModelProperty { description?: string dataSpecs?: ThingModelProperty dataSpecsList?: ThingModelProperty[] + modbusConfig?: ModbusConfig +} + +/** Modbus 配置 */ +export interface ModbusConfig { + collectionMode?: number // 数据采集模式:1-定时轮询,2-主动上报 + functionCode?: number // 功能码:0x03-读保持寄存器,0x04-读输入寄存器等(默认0x03) + registerAddress?: number // 寄存器地址(低位字节) + registerCount?: number // 寄存器数量 / 线圈数量(默认1) + pollingInterval?: number // 采集频率(秒) + fullFrame?: string // 完整的数据帧(自动生成的HEX字符串) + crcHex?: string // CRC 校验码(自动生成的HEX字符串) } /** 物模型事件 */ @@ -101,6 +113,7 @@ export interface ThingModelService { inputParams?: ThingModelParam[] outputParams?: ThingModelParam[] method?: string + modbusConfig?: ModbusConfig // 添加 Modbus 配置 } /** 物模型参数 */ @@ -197,6 +210,11 @@ export const ThingModelApi = { // 删除产品物模型 deleteThingModel: async (id: number) => { return await request.delete({ url: `/iot/thing-model/delete?id=` + id }) + }, + + // 计算 Modbus 命令(用于前端自动生成) + calculateModbusCommand: async (data: { productId: number; registerAddress: number; functionCode?: number }) => { + return await request.post({ url: `/iot/thing-model/calculate-modbus-command`, data }) } } diff --git a/src/views/iot/product/product/ProductForm.vue b/src/views/iot/product/product/ProductForm.vue index 5247e9258b750299ec0a95edeb6d5a6f84ff6929..ec3c2cb16c103d563e2d2ffd724162a2f952de6a 100644 --- a/src/views/iot/product/product/ProductForm.vue +++ b/src/views/iot/product/product/ProductForm.vue @@ -74,7 +74,7 @@ - + +
Modbus 协议自动使用 HEX 格式
+
+ + + + + + + + + +
默认值:1
@@ -132,7 +153,9 @@ const formData = ref({ deviceType: undefined, locationType: undefined, netType: undefined, - codecType: CodecTypeEnum.ALINK + codecType: CodecTypeEnum.ALINK, + protocolType: 'STANDARD', + modbusSlaveId: 1 }) const formRules = reactive({ productKey: [{ required: true, message: 'ProductKey 不能为空', trigger: 'blur' }], @@ -152,6 +175,26 @@ const formRules = reactive({ const formRef = ref() const categoryList = ref([]) // 产品分类列表 +// 计算属性:是否为 Modbus 协议 +const isModbusProtocol = computed(() => { + return formData.value.protocolType === 'MODBUS_RTU' || formData.value.protocolType === 'MODBUS_TCP' +}) + +// 处理协议类型变化 +const handleProtocolTypeChange = (value: string) => { + if (value === 'MODBUS_RTU' || value === 'MODBUS_TCP') { + // Modbus 协议自动设置数据格式为 HEX + formData.value.codecType = 'HEX' + // 设置默认从站ID + if (!formData.value.modbusSlaveId) { + formData.value.modbusSlaveId = 1 + } + } else { + // 其他协议使用 Alink 格式 + formData.value.codecType = CodecTypeEnum.ALINK + } +} + /** 打开弹窗 */ const open = async (type: string, id?: number) => { dialogVisible.value = true @@ -208,7 +251,9 @@ const resetForm = () => { deviceType: undefined, locationType: undefined, netType: undefined, - codecType: CodecTypeEnum.ALINK + codecType: CodecTypeEnum.ALINK, + protocolType: 'STANDARD', + modbusSlaveId: 1 } formRef.value?.resetFields() } @@ -218,3 +263,11 @@ const generateProductKey = () => { formData.value.productKey = generateRandomStr(16) } + + diff --git a/src/views/iot/rule/data/sink/DataSinkForm.vue b/src/views/iot/rule/data/sink/DataSinkForm.vue index a4974575efc21d8f254d808f8256dc777bba0e75..1f0f17e533b2ec1a14941701f8d257b5c2cdf583 100644 --- a/src/views/iot/rule/data/sink/DataSinkForm.vue +++ b/src/views/iot/rule/data/sink/DataSinkForm.vue @@ -25,6 +25,10 @@ + + + + + + + + + + + + + + + + +
{{ tableNameError }}
+
如果配置了字段映射,可点击“自动建表”根据字段映射自动创建数据库表
+
+ +
+ + 同步表结构 + + 根据字段映射自动添加数据库表的列 +
+ +
可选配置。如果不配置,将使用设备消息中的字段名作为数据库列名
+
+ + +
+ 启用后会自动保存设备元数据:device_id(设备ID)、tenant_id(租户ID)、report_time(上报时间) +
+
+ + + + + diff --git a/src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue b/src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue index d0b115cdc7fd75f9491bc3ed7a58de9965ed2aa7..49a55e4d88c90077116ebf7a43c6c5ac3ae4fb84 100644 --- a/src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue +++ b/src/views/iot/rule/data/sink/config/components/KeyValueEditor.vue @@ -1,7 +1,7 @@ diff --git a/src/views/iot/thingmodel/ThingModelService.vue b/src/views/iot/thingmodel/ThingModelService.vue index 35f7c988846f9d27a193544e7719a20a294c647c..ffb9574b2370e139343165cd513c89e3f81f759e 100644 --- a/src/views/iot/thingmodel/ThingModelService.vue +++ b/src/views/iot/thingmodel/ThingModelService.vue @@ -27,6 +27,52 @@ :direction="IoTThingModelParamDirectionEnum.OUTPUT" /> + + + Modbus 配置(可选) + + +
+ 如果配置了完整帧,将直接使用该帧发送命令(推荐用于固定命令) +
+
+ + +
+ 常用:05=写单线圈、06=写单寄存器、15=写多线圈、16=写多寄存器 +
+
+ + + + + +
+ 功能码05:65280(0xFF00)=闭合,0(0x0000)=断开 +
+