diff --git a/apps/web-antd/src/api/iot/device/modbus/config/index.ts b/apps/web-antd/src/api/iot/device/modbus/config/index.ts new file mode 100644 index 000000000..3ee1e4f10 --- /dev/null +++ b/apps/web-antd/src/api/iot/device/modbus/config/index.ts @@ -0,0 +1,30 @@ +import { requestClient } from '#/api/request'; + +export namespace IotDeviceModbusConfigApi { + /** Modbus 连接配置 VO */ + export interface ModbusConfig { + id?: number; // 主键 + deviceId: number; // 设备编号 + ip: string; // Modbus 服务器 IP 地址 + port: number; // Modbus 服务器端口 + slaveId: number; // 从站地址 + timeout: number; // 连接超时时间,单位:毫秒 + retryInterval: number; // 重试间隔,单位:毫秒 + mode: number; // 模式 + frameFormat: number; // 帧格式 + status: number; // 状态 + } +} + +/** 获取设备的 Modbus 连接配置 */ +export function getModbusConfig(deviceId: number) { + return requestClient.get( + '/iot/device-modbus-config/get', + { params: { deviceId } }, + ); +} + +/** 保存 Modbus 连接配置 */ +export function saveModbusConfig(data: IotDeviceModbusConfigApi.ModbusConfig) { + return requestClient.post('/iot/device-modbus-config/save', data); +} diff --git a/apps/web-antd/src/api/iot/device/modbus/point/index.ts b/apps/web-antd/src/api/iot/device/modbus/point/index.ts new file mode 100644 index 000000000..fa5c6767d --- /dev/null +++ b/apps/web-antd/src/api/iot/device/modbus/point/index.ts @@ -0,0 +1,56 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace IotDeviceModbusPointApi { + /** Modbus 点位配置 VO */ + export interface ModbusPoint { + id?: number; // 主键 + deviceId: number; // 设备编号 + thingModelId?: number; // 物模型属性编号 + identifier: string; // 属性标识符 + name: string; // 属性名称 + functionCode?: number; // Modbus 功能码 + registerAddress?: number; // 寄存器起始地址 + registerCount?: number; // 寄存器数量 + byteOrder?: string; // 字节序 + rawDataType?: string; // 原始数据类型 + scale: number; // 缩放因子 + pollInterval: number; // 轮询间隔,单位:毫秒 + status: number; // 状态 + } +} + +/** 获取设备的 Modbus 点位分页 */ +export function getModbusPointPage(params: PageParam) { + return requestClient.get>( + '/iot/device-modbus-point/page', + { params }, + ); +} + +/** 获取 Modbus 点位详情 */ +export function getModbusPoint(id: number) { + return requestClient.get( + `/iot/device-modbus-point/get?id=${id}`, + ); +} + +/** 创建 Modbus 点位配置 */ +export function createModbusPoint( + data: IotDeviceModbusPointApi.ModbusPoint, +) { + return requestClient.post('/iot/device-modbus-point/create', data); +} + +/** 更新 Modbus 点位配置 */ +export function updateModbusPoint( + data: IotDeviceModbusPointApi.ModbusPoint, +) { + return requestClient.put('/iot/device-modbus-point/update', data); +} + +/** 删除 Modbus 点位配置 */ +export function deleteModbusPoint(id: number) { + return requestClient.delete(`/iot/device-modbus-point/delete?id=${id}`); +} diff --git a/apps/web-antd/src/api/iot/product/product/index.ts b/apps/web-antd/src/api/iot/product/product/index.ts index 335819dd3..f77255f4d 100644 --- a/apps/web-antd/src/api/iot/product/product/index.ts +++ b/apps/web-antd/src/api/iot/product/product/index.ts @@ -10,7 +10,7 @@ export namespace IotProductApi { productKey?: string; // 产品标识 productSecret?: string; // 产品密钥 protocolId?: number; // 协议编号 - protocolType?: number; // 接入协议类型 + protocolType?: string; // 协议类型 categoryId?: number; // 产品所属品类标识符 categoryName?: string; // 产品所属品类名称 icon?: string; // 产品图标 @@ -19,7 +19,7 @@ export namespace IotProductApi { status?: number; // 产品状态 deviceType?: number; // 设备类型 netType?: number; // 联网方式 - codecType?: string; // 数据格式(编解码器类型) + serializeType?: string; // 序列化类型 dataFormat?: number; // 数据格式 validateType?: number; // 认证方式 registerEnabled?: boolean; // 是否开启动态注册 @@ -28,6 +28,25 @@ export namespace IotProductApi { } } +// IoT 协议类型枚举 +export enum ProtocolTypeEnum { + COAP = 'coap', + EMQX = 'emqx', + HTTP = 'http', + MODBUS_TCP_CLIENT = 'modbus_tcp_client', + MODBUS_TCP_SERVER = 'modbus_tcp_server', + MQTT = 'mqtt', + TCP = 'tcp', + UDP = 'udp', + WEBSOCKET = 'websocket', +} + +// IoT 序列化类型枚举 +export enum SerializeTypeEnum { + BINARY = 'binary', + JSON = 'json', +} + /** 查询产品分页 */ export function getProductPage(params: PageParam) { return requestClient.get>( diff --git a/apps/web-antd/src/views/iot/device/device/detail/index.vue b/apps/web-antd/src/views/iot/device/device/detail/index.vue index 4b549a363..cbe9319e7 100644 --- a/apps/web-antd/src/views/iot/device/device/detail/index.vue +++ b/apps/web-antd/src/views/iot/device/device/detail/index.vue @@ -12,13 +12,14 @@ import { DeviceTypeEnum } from '@vben/constants'; import { message, Tabs } from 'ant-design-vue'; import { getDevice } from '#/api/iot/device/device'; -import { getProduct } from '#/api/iot/product/product'; +import { getProduct, ProtocolTypeEnum } from '#/api/iot/product/product'; import { getThingModelListByProductId } from '#/api/iot/thingmodel'; import DeviceDetailConfig from './modules/config.vue'; import DeviceDetailsHeader from './modules/header.vue'; import DeviceDetailsInfo from './modules/info.vue'; import DeviceDetailsMessage from './modules/message.vue'; +import DeviceModbusConfig from './modules/modbus-config.vue'; import DeviceDetailsSimulator from './modules/simulator.vue'; import DeviceDetailsSubDevice from './modules/sub-device.vue'; import DeviceDetailsThingModel from './modules/thing-model.vue'; @@ -141,6 +142,23 @@ onMounted(async () => { @success="() => getDeviceData(id)" /> + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config-form.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config-form.vue new file mode 100644 index 000000000..fb792d4e7 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config-form.vue @@ -0,0 +1,222 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config.vue new file mode 100644 index 000000000..e0d366027 --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-config.vue @@ -0,0 +1,358 @@ + + + + diff --git a/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-point-form.vue b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-point-form.vue new file mode 100644 index 000000000..755ac1a2d --- /dev/null +++ b/apps/web-antd/src/views/iot/device/device/detail/modules/modbus-point-form.vue @@ -0,0 +1,314 @@ + + + + diff --git a/apps/web-antd/src/views/iot/product/product/data.ts b/apps/web-antd/src/views/iot/product/product/data.ts index fd02e9f09..f2c8fcb6c 100644 --- a/apps/web-antd/src/views/iot/product/product/data.ts +++ b/apps/web-antd/src/views/iot/product/product/data.ts @@ -127,16 +127,26 @@ export function useBasicFormSchema( rules: 'required', }, { - fieldName: 'codecType', - label: '数据格式', - component: 'RadioGroup', + fieldName: 'protocolType', + label: '协议类型', + component: 'Select', componentProps: { - options: getDictOptions(DICT_TYPE.IOT_CODEC_TYPE, 'string'), - buttonStyle: 'solid', - optionType: 'button', + options: getDictOptions(DICT_TYPE.IOT_PROTOCOL_TYPE, 'string'), + placeholder: '请选择协议类型', }, rules: 'required', }, + { + fieldName: 'serializeType', + label: '序列化类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.IOT_SERIALIZE_TYPE, 'string'), + placeholder: '请选择序列化类型', + }, + help: 'iot-gateway-server 默认根据接入的协议类型确定数据格式,仅 MQTT、EMQX 协议支持自定义序列化类型', + rules: 'required', + }, // TODO @haohao:这个貌似不需要?! { fieldName: 'status', diff --git a/apps/web-antd/src/views/iot/product/product/detail/modules/info.vue b/apps/web-antd/src/views/iot/product/product/detail/modules/info.vue index 674a77284..74f1156ac 100644 --- a/apps/web-antd/src/views/iot/product/product/detail/modules/info.vue +++ b/apps/web-antd/src/views/iot/product/product/detail/modules/info.vue @@ -57,8 +57,17 @@ async function copyToClipboard(text: string) { {{ formatDate(product.createTime) }} - - {{ product.codecType || '-' }} + + + + + diff --git a/apps/web-antd/src/views/iot/utils/constants.ts b/apps/web-antd/src/views/iot/utils/constants.ts index ef7a8a6df..c66c9277e 100644 --- a/apps/web-antd/src/views/iot/utils/constants.ts +++ b/apps/web-antd/src/views/iot/utils/constants.ts @@ -522,3 +522,135 @@ export const JSON_PARAMS_EXAMPLE_VALUES: Record = { [IoTDataSpecsDataTypeEnum.ARRAY]: { display: '[]', value: [] }, DEFAULT: { display: '""', value: '' }, }; + +// ========== Modbus 通用常量 ========== + +/** Modbus 模式枚举 */ +export const ModbusModeEnum = { + POLLING: 1, // 云端轮询 + ACTIVE_REPORT: 2, // 主动上报 +} as const; + +/** Modbus 帧格式枚举 */ +export const ModbusFrameFormatEnum = { + MODBUS_TCP: 1, // Modbus TCP + MODBUS_RTU: 2, // Modbus RTU +} as const; + +/** Modbus 功能码枚举 */ +export const ModbusFunctionCodeEnum = { + READ_COILS: 1, // 读线圈 + READ_DISCRETE_INPUTS: 2, // 读离散输入 + READ_HOLDING_REGISTERS: 3, // 读保持寄存器 + READ_INPUT_REGISTERS: 4, // 读输入寄存器 +} as const; + +/** Modbus 功能码选项 */ +export const ModbusFunctionCodeOptions = [ + { value: 1, label: '01 - 读线圈 (Coils)', description: '可读写布尔值' }, + { + value: 2, + label: '02 - 读离散输入 (Discrete Inputs)', + description: '只读布尔值', + }, + { + value: 3, + label: '03 - 读保持寄存器 (Holding Registers)', + description: '可读写 16 位数据', + }, + { + value: 4, + label: '04 - 读输入寄存器 (Input Registers)', + description: '只读 16 位数据', + }, +]; + +/** Modbus 原始数据类型枚举 */ +export const ModbusRawDataTypeEnum = { + INT16: 'INT16', + UINT16: 'UINT16', + INT32: 'INT32', + UINT32: 'UINT32', + FLOAT: 'FLOAT', + DOUBLE: 'DOUBLE', + BOOLEAN: 'BOOLEAN', + STRING: 'STRING', +} as const; + +/** Modbus 原始数据类型选项 */ +export const ModbusRawDataTypeOptions = [ + { + value: 'INT16', + label: 'INT16', + description: '有符号16位整数', + registerCount: 1, + }, + { + value: 'UINT16', + label: 'UINT16', + description: '无符号16位整数', + registerCount: 1, + }, + { + value: 'INT32', + label: 'INT32', + description: '有符号32位整数', + registerCount: 2, + }, + { + value: 'UINT32', + label: 'UINT32', + description: '无符号32位整数', + registerCount: 2, + }, + { + value: 'FLOAT', + label: 'FLOAT', + description: '32位浮点数', + registerCount: 2, + }, + { + value: 'DOUBLE', + label: 'DOUBLE', + description: '64位浮点数', + registerCount: 4, + }, + { + value: 'BOOLEAN', + label: 'BOOLEAN', + description: '布尔值', + registerCount: 1, + }, + { + value: 'STRING', + label: 'STRING', + description: '字符串', + registerCount: 0, + }, +]; + +/** Modbus 字节序选项 - 16位 */ +export const ModbusByteOrder16Options = [ + { value: 'AB', label: 'AB', description: '大端序' }, + { value: 'BA', label: 'BA', description: '小端序' }, +]; + +/** Modbus 字节序选项 - 32位 */ +export const ModbusByteOrder32Options = [ + { value: 'ABCD', label: 'ABCD', description: '大端序' }, + { value: 'CDAB', label: 'CDAB', description: '大端字交换' }, + { value: 'DCBA', label: 'DCBA', description: '小端序' }, + { value: 'BADC', label: 'BADC', description: '小端字交换' }, +]; + +/** 根据数据类型获取字节序选项 */ +export const getByteOrderOptions = (rawDataType: string) => { + if (['FLOAT', 'INT32', 'UINT32'].includes(rawDataType)) { + return ModbusByteOrder32Options; + } + if (rawDataType === 'DOUBLE') { + // 64 位暂时复用 32 位字节序 + return ModbusByteOrder32Options; + } + return ModbusByteOrder16Options; +}; diff --git a/packages/constants/src/dict-enum.ts b/packages/constants/src/dict-enum.ts index 73a5c57dc..e9477615b 100644 --- a/packages/constants/src/dict-enum.ts +++ b/packages/constants/src/dict-enum.ts @@ -150,7 +150,7 @@ const AI_DICT = { const IOT_DICT = { IOT_ALERT_LEVEL: 'iot_alert_level', // IoT 告警级别 IOT_ALERT_RECEIVE_TYPE: 'iot_alert_receive_type', // IoT 告警接收类型 - IOT_CODEC_TYPE: 'iot_codec_type', // IOT 数据格式(编解码器类型) + IOT_SERIALIZE_TYPE: 'iot_serialize_type', // IOT 序列化类型 IOT_DATA_FORMAT: 'iot_data_format', // IOT 数据格式 IOT_DATA_SINK_TYPE_ENUM: 'iot_data_sink_type_enum', // IoT 数据流转目的类型 IOT_DATA_TYPE: 'iot_data_type', // IOT 数据类型 @@ -171,6 +171,8 @@ const IOT_DICT = { IOT_THING_MODEL_UNIT: 'iot_thing_model_unit', // IOT 物模型单位 IOT_UNIT_TYPE: 'iot_unit_type', // IOT 单位类型 IOT_VALIDATE_TYPE: 'iot_validate_type', // IOT 数据校验级别 + IOT_MODBUS_MODE: 'iot_modbus_mode', // IoT Modbus 工作模式 + IOT_MODBUS_FRAME_FORMAT: 'iot_modbus_frame_format', // IoT Modbus 帧格式 } as const; /** 字典类型枚举 - 统一导出 */