【物联网iot】:新增tcp/mqtt透传modbus rtu/modbus tcp,支持定时轮询,查看modbus rtu命令
parent
423af80a82
commit
eea1e1eca9
|
|
@ -16,6 +16,8 @@ export interface ProductVO {
|
||||||
locationType: number // 设备类型
|
locationType: number // 设备类型
|
||||||
netType: number // 联网方式
|
netType: number // 联网方式
|
||||||
codecType: string // 数据格式(编解码器类型)
|
codecType: string // 数据格式(编解码器类型)
|
||||||
|
protocolType?: string // 协议类型(STANDARD, MODBUS_RTU, MODBUS_TCP)
|
||||||
|
modbusSlaveId?: number // Modbus从站ID(1-247)
|
||||||
deviceCount: number // 设备数量
|
deviceCount: number // 设备数量
|
||||||
createTime: Date // 创建时间
|
createTime: Date // 创建时间
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,18 @@ export interface ThingModelProperty {
|
||||||
description?: string
|
description?: string
|
||||||
dataSpecs?: ThingModelProperty
|
dataSpecs?: ThingModelProperty
|
||||||
dataSpecsList?: 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[]
|
inputParams?: ThingModelParam[]
|
||||||
outputParams?: ThingModelParam[]
|
outputParams?: ThingModelParam[]
|
||||||
method?: string
|
method?: string
|
||||||
|
modbusConfig?: ModbusConfig // 添加 Modbus 配置
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 物模型参数 */
|
/** 物模型参数 */
|
||||||
|
|
@ -197,6 +210,11 @@ export const ThingModelApi = {
|
||||||
// 删除产品物模型
|
// 删除产品物模型
|
||||||
deleteThingModel: async (id: number) => {
|
deleteThingModel: async (id: number) => {
|
||||||
return await request.delete({ url: `/iot/thing-model/delete?id=` + id })
|
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ const getList = async () => {
|
||||||
if (!props.deviceId) return
|
if (!props.deviceId) return
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
|
queryParams.deviceId = props.deviceId
|
||||||
const data = await DeviceApi.getDeviceMessagePairPage(queryParams)
|
const data = await DeviceApi.getDeviceMessagePairPage(queryParams)
|
||||||
list.value = data.list
|
list.value = data.list
|
||||||
total.value = data.length
|
total.value = data.length
|
||||||
|
|
@ -139,6 +140,17 @@ const getList = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 监听 deviceId 变化 */
|
||||||
|
watch(
|
||||||
|
() => props.deviceId,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
queryParams.deviceId = val
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,9 @@ const queryFormRef = ref() // 搜索的表单
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
|
if (!props.deviceId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
|
|
@ -185,6 +188,16 @@ const getList = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 监听 deviceId 变化 */
|
||||||
|
watch(
|
||||||
|
() => props.deviceId,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
/** 前端筛选数据 */
|
/** 前端筛选数据 */
|
||||||
const handleFilter = () => {
|
const handleFilter = () => {
|
||||||
if (!queryParams.keyword.trim()) {
|
if (!queryParams.keyword.trim()) {
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ const getList = async () => {
|
||||||
if (!props.deviceId) return
|
if (!props.deviceId) return
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
|
queryParams.deviceId = props.deviceId
|
||||||
const data = await DeviceApi.getDeviceMessagePairPage(queryParams)
|
const data = await DeviceApi.getDeviceMessagePairPage(queryParams)
|
||||||
list.value = data.list
|
list.value = data.list
|
||||||
total.value = data.length
|
total.value = data.length
|
||||||
|
|
@ -154,6 +155,16 @@ const getList = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 监听 deviceId 变化 */
|
||||||
|
watch(
|
||||||
|
() => props.deviceId,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
queryParams.deviceId = val
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,28 @@
|
||||||
{{ dict.label }}
|
{{ dict.label }}
|
||||||
</el-radio>
|
</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
<div v-if="isModbusProtocol" class="form-tip modbus-tip">Modbus 协议自动使用 HEX 格式</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="协议类型" prop="protocolType">
|
||||||
|
<el-select
|
||||||
|
v-model="formData.protocolType"
|
||||||
|
placeholder="请选择协议类型"
|
||||||
|
clearable
|
||||||
|
@change="handleProtocolTypeChange"
|
||||||
|
>
|
||||||
|
<el-option label="标准协议(JSON)" value="STANDARD" />
|
||||||
|
<el-option label="Modbus RTU" value="MODBUS_RTU" />
|
||||||
|
<el-option label="Modbus TCP" value="MODBUS_TCP" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="isModbusProtocol" label="Modbus从站ID" prop="modbusSlaveId">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formData.modbusSlaveId"
|
||||||
|
:min="1"
|
||||||
|
:max="247"
|
||||||
|
placeholder="请输入从站ID(1-247)"
|
||||||
|
/>
|
||||||
|
<div class="form-tip">默认值:1</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-collapse>
|
<el-collapse>
|
||||||
<el-collapse-item title="更多配置">
|
<el-collapse-item title="更多配置">
|
||||||
|
|
@ -132,7 +154,9 @@ const formData = ref({
|
||||||
deviceType: undefined,
|
deviceType: undefined,
|
||||||
locationType: undefined,
|
locationType: undefined,
|
||||||
netType: undefined,
|
netType: undefined,
|
||||||
codecType: CodecTypeEnum.ALINK
|
codecType: CodecTypeEnum.ALINK,
|
||||||
|
protocolType: 'STANDARD',
|
||||||
|
modbusSlaveId: 1
|
||||||
})
|
})
|
||||||
const formRules = reactive({
|
const formRules = reactive({
|
||||||
productKey: [{ required: true, message: 'ProductKey 不能为空', trigger: 'blur' }],
|
productKey: [{ required: true, message: 'ProductKey 不能为空', trigger: 'blur' }],
|
||||||
|
|
@ -151,7 +175,30 @@ const formRules = reactive({
|
||||||
})
|
})
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const categoryList = ref<ProductCategoryVO[]>([]) // 产品分类列表
|
const categoryList = ref<ProductCategoryVO[]>([]) // 产品分类列表
|
||||||
|
// 计算属性:是否为 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
|
||||||
|
// 查找已存在的 HEX 选项(忽略大小写)
|
||||||
|
const options = getStrDictOptions(DICT_TYPE.IOT_CODEC_TYPE)
|
||||||
|
const hexOption = options.find((item) => item.value.toUpperCase() === 'HEX')
|
||||||
|
formData.value.codecType = hexOption ? hexOption.value : 'HEX' // 如果存在则使用存在的 options 值,否则使用 'HEX'
|
||||||
|
// 设置默认从站ID
|
||||||
|
if (!formData.value.modbusSlaveId) {
|
||||||
|
formData.value.modbusSlaveId = 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 其他协议使用 Alink 格式
|
||||||
|
formData.value.codecType = CodecTypeEnum.ALINK
|
||||||
|
}
|
||||||
|
}
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (type: string, id?: number) => {
|
const open = async (type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -208,7 +255,9 @@ const resetForm = () => {
|
||||||
deviceType: undefined,
|
deviceType: undefined,
|
||||||
locationType: undefined,
|
locationType: undefined,
|
||||||
netType: undefined,
|
netType: undefined,
|
||||||
codecType: CodecTypeEnum.ALINK
|
codecType: CodecTypeEnum.ALINK,
|
||||||
|
protocolType: 'STANDARD',
|
||||||
|
modbusSlaveId: 1
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
|
@ -218,3 +267,15 @@ const generateProductKey = () => {
|
||||||
formData.value.productKey = generateRandomStr(16)
|
formData.value.productKey = generateRandomStr(16)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.modbus-tip {
|
||||||
|
color: #f56c6c;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
<!-- Modbus 命令详情对话框 -->
|
||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="Modbus 命令详情" width="700px">
|
||||||
|
<el-alert
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
此命令由系统根据您配置的 Modbus 参数自动生成,可直接用于设备通信
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
|
||||||
|
<el-descriptions :column="1" border v-loading="loading">
|
||||||
|
<!-- 完整数据帧 -->
|
||||||
|
<el-descriptions-item label="完整数据帧">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<el-tag type="primary" size="large" class="font-mono">
|
||||||
|
{{ modbusConfig?.fullFrame || '-' }}
|
||||||
|
</el-tag>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
:icon="CopyDocument"
|
||||||
|
@click="copyToClipboard(modbusConfig?.fullFrame)"
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-descriptions-item>
|
||||||
|
|
||||||
|
<!-- CRC 校验码 -->
|
||||||
|
<el-descriptions-item label="CRC 校验码">
|
||||||
|
<el-tag type="success" class="font-mono">
|
||||||
|
{{ modbusConfig?.crcHex || '-' }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
|
||||||
|
<!-- 功能码 -->
|
||||||
|
<el-descriptions-item label="功能码">
|
||||||
|
<el-tag type="warning">
|
||||||
|
{{ formatFunctionCode(modbusConfig?.functionCode) }}
|
||||||
|
</el-tag>
|
||||||
|
<span class="ml-2 text-gray-500">{{ getFunctionCodeDesc(modbusConfig?.functionCode) }}</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
|
||||||
|
<!-- 寄存器地址 -->
|
||||||
|
<el-descriptions-item label="寄存器地址">
|
||||||
|
<el-tag>{{ modbusConfig?.registerAddress ?? '-' }}</el-tag>
|
||||||
|
<span class="ml-2 text-gray-500">(十进制)</span>
|
||||||
|
</el-descriptions-item>
|
||||||
|
|
||||||
|
<!-- 寄存器数量 -->
|
||||||
|
<el-descriptions-item label="寄存器数量">
|
||||||
|
<el-tag>{{ modbusConfig?.registerCount ?? '-' }}</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
|
||||||
|
<!-- 字节序 -->
|
||||||
|
<el-descriptions-item label="字节序">
|
||||||
|
<el-tag :type="modbusConfig?.byteOrder === 'BIG_ENDIAN' ? 'primary' : 'info'">
|
||||||
|
{{ getByteOrderLabel(modbusConfig?.byteOrder) }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
|
||||||
|
<!-- 数据采集模式 -->
|
||||||
|
<el-descriptions-item label="数据采集模式">
|
||||||
|
<el-tag :type="modbusConfig?.collectionMode === 1 ? 'success' : 'warning'">
|
||||||
|
{{ getCollectionModeLabel(modbusConfig?.collectionMode) }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
|
||||||
|
<!-- 采集频率(仅定时轮询模式) -->
|
||||||
|
<el-descriptions-item
|
||||||
|
v-if="modbusConfig?.collectionMode === 1"
|
||||||
|
label="采集频率"
|
||||||
|
>
|
||||||
|
<el-tag type="info">
|
||||||
|
{{ modbusConfig?.pollingInterval ?? '-' }} 秒
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CopyDocument } from '@element-plus/icons-vue'
|
||||||
|
import { ModbusConfig, ThingModelApi } from '@/api/iot/thingmodel'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
|
import { IoTModbusCollectionModeEnum, IoTModbusByteOrderEnum } from '@/views/iot/utils/constants'
|
||||||
|
|
||||||
|
defineOptions({ name: 'ThingModelModbusCommandDialog' })
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const { copy } = useClipboard()
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const modbusConfig = ref<ModbusConfig | null>(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
/** 打开对话框 */
|
||||||
|
const open = async (config: ModbusConfig, productId: number) => {
|
||||||
|
modbusConfig.value = { ...config }
|
||||||
|
dialogVisible.value = true
|
||||||
|
|
||||||
|
// 如果 fullFrame 为空,实时生成命令
|
||||||
|
if (!config.fullFrame && config.registerAddress !== undefined && config.functionCode) {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const result = await ThingModelApi.calculateModbusCommand({
|
||||||
|
productId: productId,
|
||||||
|
registerAddress: config.registerAddress,
|
||||||
|
functionCode: config.functionCode,
|
||||||
|
registerCount: config.registerCount,
|
||||||
|
byteOrder: config.byteOrder
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('API 返回结果:', result)
|
||||||
|
|
||||||
|
// 更新命令数据
|
||||||
|
if (modbusConfig.value && result) {
|
||||||
|
modbusConfig.value.fullFrame = result.fullFrame
|
||||||
|
modbusConfig.value.crcHex = result.crcHex
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('生成 Modbus 命令失败', error)
|
||||||
|
message.error('生成命令失败: ' + (error.message || '未知错误'))
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
|
||||||
|
/** 格式化功能码 */
|
||||||
|
const formatFunctionCode = (code: number | undefined) => {
|
||||||
|
if (code === undefined) return '-'
|
||||||
|
return `0x${code.toString(16).toUpperCase().padStart(2, '0')} (${code})`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取功能码描述 */
|
||||||
|
const getFunctionCodeDesc = (code: number | undefined) => {
|
||||||
|
const descriptions: Record<number, string> = {
|
||||||
|
1: '读线圈',
|
||||||
|
2: '读离散输入',
|
||||||
|
3: '读保持寄存器',
|
||||||
|
4: '读输入寄存器',
|
||||||
|
5: '写单个线圈',
|
||||||
|
6: '写单个寄存器',
|
||||||
|
15: '写多个线圈',
|
||||||
|
16: '写多个寄存器'
|
||||||
|
}
|
||||||
|
return code !== undefined ? descriptions[code] || '自定义功能码' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取字节序标签 */
|
||||||
|
const getByteOrderLabel = (byteOrder: string | undefined) => {
|
||||||
|
if (!byteOrder) return '-'
|
||||||
|
const order = Object.values(IoTModbusByteOrderEnum).find(o => o.value === byteOrder)
|
||||||
|
return order ? order.label : byteOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取采集模式标签 */
|
||||||
|
const getCollectionModeLabel = (mode: number | undefined) => {
|
||||||
|
if (mode === undefined) return '-'
|
||||||
|
const modeEnum = Object.values(IoTModbusCollectionModeEnum).find(m => m.value === mode)
|
||||||
|
return modeEnum ? modeEnum.label : mode.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 复制到剪贴板 */
|
||||||
|
const copyToClipboard = async (text: string | undefined) => {
|
||||||
|
if (!text) {
|
||||||
|
message.warning('没有可复制的内容')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await copy(text)
|
||||||
|
message.success('已复制到剪贴板')
|
||||||
|
} catch (error) {
|
||||||
|
message.error('复制失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.font-mono {
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-descriptions__label) {
|
||||||
|
width: 120px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tag) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -93,6 +93,91 @@
|
||||||
</el-radio>
|
</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<!-- Modbus 配置区域 -->
|
||||||
|
<div v-if="isModbusProduct && !isStructDataSpecs && !isParams" class="modbus-config-section">
|
||||||
|
<el-divider content-position="left">Modbus 配置</el-divider>
|
||||||
|
<!-- 数据采集模式 -->
|
||||||
|
<el-form-item label="数据采集模式" prop="property.modbusConfig.collectionMode">
|
||||||
|
<el-radio-group
|
||||||
|
v-model="property.modbusConfig.collectionMode"
|
||||||
|
@change="handleCollectionModeChange"
|
||||||
|
>
|
||||||
|
<el-radio
|
||||||
|
v-for="mode in Object.values(IoTModbusCollectionModeEnum)"
|
||||||
|
:key="mode.value"
|
||||||
|
:label="mode.value"
|
||||||
|
>
|
||||||
|
{{ mode.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 采集频率(仅在定时轮询模式下显示) -->
|
||||||
|
<el-form-item
|
||||||
|
v-if="property.modbusConfig.collectionMode === IoTModbusCollectionModeEnum.POLLING.value"
|
||||||
|
label="采集频率"
|
||||||
|
prop="property.modbusConfig.pollingInterval"
|
||||||
|
>
|
||||||
|
<el-input-number
|
||||||
|
v-model="property.modbusConfig.pollingInterval"
|
||||||
|
:min="10"
|
||||||
|
:max="86400"
|
||||||
|
placeholder="请输入采集频率(10-86400秒)"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 功能码 -->
|
||||||
|
<el-form-item label="功能码" prop="property.modbusConfig.functionCode">
|
||||||
|
<el-input-number
|
||||||
|
v-model="property.modbusConfig.functionCode"
|
||||||
|
:min="1"
|
||||||
|
:max="255"
|
||||||
|
placeholder="请输入功能码(1-255)"
|
||||||
|
@change="handleFunctionCodeChange"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 寄存器地址 -->
|
||||||
|
<el-form-item label="寄存器地址" prop="property.modbusConfig.registerAddress">
|
||||||
|
<el-input-number
|
||||||
|
v-model="property.modbusConfig.registerAddress"
|
||||||
|
:min="0"
|
||||||
|
:max="65535"
|
||||||
|
placeholder="请输入寄存器地址(0-65535)"
|
||||||
|
@change="handleRegisterAddressChange"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 寄存器数量 / 线圈数量 -->
|
||||||
|
<el-form-item label="寄存器数量" prop="property.modbusConfig.registerCount">
|
||||||
|
<el-input-number
|
||||||
|
v-model="property.modbusConfig.registerCount"
|
||||||
|
:min="1"
|
||||||
|
:max="255"
|
||||||
|
placeholder="请输入寄存器数量(1-255)"
|
||||||
|
@change="handleRegisterCountChange"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 字节序 -->
|
||||||
|
<el-form-item label="字节序" prop="property.modbusConfig.byteOrder">
|
||||||
|
<el-radio-group v-model="property.modbusConfig.byteOrder">
|
||||||
|
<el-radio
|
||||||
|
v-for="order in Object.values(IoTModbusByteOrderEnum)"
|
||||||
|
:key="order.value"
|
||||||
|
:label="order.value"
|
||||||
|
>
|
||||||
|
{{ order.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 自动生成的命令(只读显示) -->
|
||||||
|
<el-form-item label="完整数据帧" v-if="property.modbusConfig?.fullFrame">
|
||||||
|
<el-input v-model="property.modbusConfig.fullFrame" readonly placeholder="自动生成" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="CRC校验码" v-if="property.modbusConfig?.crcHex">
|
||||||
|
<el-input v-model="property.modbusConfig.crcHex" readonly placeholder="自动生成" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
@ -103,13 +188,19 @@ import {
|
||||||
ThingModelNumberDataSpecs,
|
ThingModelNumberDataSpecs,
|
||||||
ThingModelStructDataSpecs
|
ThingModelStructDataSpecs
|
||||||
} from './dataSpecs'
|
} from './dataSpecs'
|
||||||
import { ThingModelProperty, validateBoolName } from '@/api/iot/thingmodel'
|
import { ThingModelApi, ThingModelProperty, validateBoolName } from '@/api/iot/thingmodel'
|
||||||
import { isEmpty } from '@/utils/is'
|
import { isEmpty } from '@/utils/is'
|
||||||
import {
|
import {
|
||||||
getDataTypeOptions,
|
getDataTypeOptions,
|
||||||
IoTDataSpecsDataTypeEnum,
|
IoTDataSpecsDataTypeEnum,
|
||||||
IoTThingModelAccessModeEnum
|
IoTThingModelAccessModeEnum,
|
||||||
|
IoTModbusCollectionModeEnum,
|
||||||
|
IoTModbusByteOrderEnum,
|
||||||
|
IOT_PROVIDE_KEY
|
||||||
} from '@/views/iot/utils/constants'
|
} from '@/views/iot/utils/constants'
|
||||||
|
import { ProductVO } from '@/api/iot/product/product'
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
|
|
||||||
/** IoT 物模型属性 */
|
/** IoT 物模型属性 */
|
||||||
defineOptions({ name: 'ThingModelProperty' })
|
defineOptions({ name: 'ThingModelProperty' })
|
||||||
|
|
@ -117,6 +208,14 @@ defineOptions({ name: 'ThingModelProperty' })
|
||||||
const props = defineProps<{ modelValue: any; isStructDataSpecs?: boolean; isParams?: boolean }>()
|
const props = defineProps<{ modelValue: any; isStructDataSpecs?: boolean; isParams?: boolean }>()
|
||||||
const emits = defineEmits(['update:modelValue'])
|
const emits = defineEmits(['update:modelValue'])
|
||||||
const property = useVModel(props, 'modelValue', emits) as Ref<ThingModelProperty>
|
const property = useVModel(props, 'modelValue', emits) as Ref<ThingModelProperty>
|
||||||
|
const product = inject<Ref<ProductVO>>(IOT_PROVIDE_KEY.PRODUCT) // 注入产品信息
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
// 计算属性:是否为 Modbus 产品
|
||||||
|
const isModbusProduct = computed(() => {
|
||||||
|
if (!product?.value) return false
|
||||||
|
return product.value.protocolType === 'MODBUS_RTU' || product.value.protocolType === 'MODBUS_TCP'
|
||||||
|
})
|
||||||
const getDataTypeOptions2 = computed(() => {
|
const getDataTypeOptions2 = computed(() => {
|
||||||
if (!props.isStructDataSpecs) {
|
if (!props.isStructDataSpecs) {
|
||||||
return getDataTypeOptions()
|
return getDataTypeOptions()
|
||||||
|
|
@ -166,6 +265,96 @@ watch(
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
/** 初始化 Modbus 配置 */
|
||||||
|
watch(
|
||||||
|
() => isModbusProduct.value,
|
||||||
|
(isModbus) => {
|
||||||
|
if (isModbus && !property.value.modbusConfig) {
|
||||||
|
property.value.modbusConfig = {
|
||||||
|
collectionMode: IoTModbusCollectionModeEnum.POLLING.value, // 默认为定时轮询模式
|
||||||
|
functionCode: undefined, // 功能码由用户输入,不设置默认值
|
||||||
|
registerAddress: undefined,
|
||||||
|
registerCount: 1, // 默认读取 1 个寄存器
|
||||||
|
pollingInterval: 300, // 默认采集频率 300 秒(5分钟)
|
||||||
|
fullFrame: '',
|
||||||
|
crcHex: ''
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
isModbus &&
|
||||||
|
property.value.modbusConfig &&
|
||||||
|
!property.value.modbusConfig.collectionMode
|
||||||
|
) {
|
||||||
|
// 向下兼容:如果没有 collectionMode,默认设置为定时轮询
|
||||||
|
property.value.modbusConfig.collectionMode = IoTModbusCollectionModeEnum.POLLING.value
|
||||||
|
}
|
||||||
|
// 向下兼容:如果没有 registerCount,默认设置为 1
|
||||||
|
if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.registerCount) {
|
||||||
|
property.value.modbusConfig.registerCount = 1
|
||||||
|
}
|
||||||
|
// 向下兼容:如果没有 pollingInterval,默认设置为 300
|
||||||
|
if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.pollingInterval) {
|
||||||
|
property.value.modbusConfig.pollingInterval = 300
|
||||||
|
}
|
||||||
|
// 向下兼容:如果没有 byteOrder,默认设置为大端模式
|
||||||
|
if (isModbus && property.value.modbusConfig && !property.value.modbusConfig.byteOrder) {
|
||||||
|
property.value.modbusConfig.byteOrder = IoTModbusByteOrderEnum.BIG_ENDIAN.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 寄存器地址变化时自动计算命令 */
|
||||||
|
const handleRegisterAddressChange = async (registerAddress: number | undefined) => {
|
||||||
|
if (!registerAddress || !product?.value?.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await ThingModelApi.calculateModbusCommand({
|
||||||
|
productId: product.value.id,
|
||||||
|
registerAddress: registerAddress,
|
||||||
|
functionCode: property.value.modbusConfig?.functionCode || 3
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新表单数据
|
||||||
|
if (!property.value.modbusConfig) {
|
||||||
|
property.value.modbusConfig = {}
|
||||||
|
}
|
||||||
|
property.value.modbusConfig.fullFrame = data.fullFrame
|
||||||
|
property.value.modbusConfig.crcHex = data.crcHex
|
||||||
|
|
||||||
|
message.success('Modbus 命令已自动生成')
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('生成 Modbus 命令失败', error)
|
||||||
|
message.error('生成命令失败:' + (error.message || '未知错误'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 功能码变化时重新计算命令 */
|
||||||
|
const handleFunctionCodeChange = async (functionCode: number | undefined) => {
|
||||||
|
// 如果寄存器地址已设置,则重新生成命令
|
||||||
|
if (property.value.modbusConfig?.registerAddress) {
|
||||||
|
await handleRegisterAddressChange(property.value.modbusConfig.registerAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 寄存器数量变化时重新计算命令 */
|
||||||
|
const handleRegisterCountChange = async (registerCount: number | undefined) => {
|
||||||
|
// 如果寄存器地址已设置,则重新生成命令
|
||||||
|
if (property.value.modbusConfig?.registerAddress) {
|
||||||
|
await handleRegisterAddressChange(property.value.modbusConfig.registerAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 采集模式变化时的处理 */
|
||||||
|
const handleCollectionModeChange = (mode: number) => {
|
||||||
|
// 如果切换到定时轮询模式,且没有设置采集频率,则设置默认值
|
||||||
|
if (mode === IoTModbusCollectionModeEnum.POLLING.value) {
|
||||||
|
if (property.value.modbusConfig && !property.value.modbusConfig.pollingInterval) {
|
||||||
|
property.value.modbusConfig.pollingInterval = 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
@ -174,4 +363,11 @@ watch(
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.modbus-config-section {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modbus-config-section .el-divider {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,49 @@
|
||||||
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
|
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<!-- Modbus 配置 -->
|
||||||
|
<el-divider content-position="left">Modbus 配置(可选)</el-divider>
|
||||||
|
<el-form-item label="完整帧">
|
||||||
|
<el-input
|
||||||
|
v-model="service.modbusConfig.fullFrame"
|
||||||
|
placeholder="十六进制完整帧,如:40050000FF00832B"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<div class="form-item-tip"> 如果配置了完整帧,将直接使用该帧发送命令(推荐用于固定命令) </div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="功能码">
|
||||||
|
<el-input-number
|
||||||
|
v-model="service.modbusConfig.functionCode"
|
||||||
|
:min="1"
|
||||||
|
:max="127"
|
||||||
|
placeholder="如:5"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="寄存器地址">
|
||||||
|
<el-input-number
|
||||||
|
v-model="service.modbusConfig.registerAddress"
|
||||||
|
:min="0"
|
||||||
|
:max="65535"
|
||||||
|
placeholder="如:0"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="写入值">
|
||||||
|
<el-input-number v-model="service.modbusConfig.writeValue" placeholder="写入值" />
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 字节序 -->
|
||||||
|
<el-form-item label="字节序" prop="service.modbusConfig.byteOrder">
|
||||||
|
<el-radio-group v-model="service.modbusConfig.byteOrder">
|
||||||
|
<el-radio
|
||||||
|
v-for="order in Object.values(IoTModbusByteOrderEnum)"
|
||||||
|
:key="order.value"
|
||||||
|
:label="order.value"
|
||||||
|
>
|
||||||
|
{{ order.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
@ -36,7 +79,8 @@ import { ThingModelService } from '@/api/iot/thingmodel'
|
||||||
import { isEmpty } from '@/utils/is'
|
import { isEmpty } from '@/utils/is'
|
||||||
import {
|
import {
|
||||||
IoTThingModelParamDirectionEnum,
|
IoTThingModelParamDirectionEnum,
|
||||||
IoTThingModelServiceCallTypeEnum
|
IoTThingModelServiceCallTypeEnum,
|
||||||
|
IoTModbusByteOrderEnum
|
||||||
} from '@/views/iot/utils/constants'
|
} from '@/views/iot/utils/constants'
|
||||||
|
|
||||||
/** IoT 物模型服务 */
|
/** IoT 物模型服务 */
|
||||||
|
|
@ -46,6 +90,22 @@ const props = defineProps<{ modelValue: any; isStructDataSpecs?: boolean }>()
|
||||||
const emits = defineEmits(['update:modelValue'])
|
const emits = defineEmits(['update:modelValue'])
|
||||||
const service = useVModel(props, 'modelValue', emits) as Ref<ThingModelService>
|
const service = useVModel(props, 'modelValue', emits) as Ref<ThingModelService>
|
||||||
|
|
||||||
|
/** 初始化 modbusConfig */
|
||||||
|
if (!service.value.modbusConfig) {
|
||||||
|
service.value.modbusConfig = {
|
||||||
|
functionCode: undefined,
|
||||||
|
registerAddress: undefined,
|
||||||
|
writeValue: undefined,
|
||||||
|
registerCount: undefined,
|
||||||
|
fullFrame: '',
|
||||||
|
crcHex: '',
|
||||||
|
byteOrder: IoTModbusByteOrderEnum.BIG_ENDIAN.value // 默认大端模式
|
||||||
|
}
|
||||||
|
} else if (!service.value.modbusConfig.byteOrder) {
|
||||||
|
// 向下兼容:如果没有 byteOrder,默认设置为大端模式
|
||||||
|
service.value.modbusConfig.byteOrder = IoTModbusByteOrderEnum.BIG_ENDIAN.value
|
||||||
|
}
|
||||||
|
|
||||||
/** 默认选中,ASYNC 异步 */
|
/** 默认选中,ASYNC 异步 */
|
||||||
watch(
|
watch(
|
||||||
() => service.value.callType,
|
() => service.value.callType,
|
||||||
|
|
@ -61,4 +121,10 @@ watch(
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.form-item-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
<DataDefinition :data="row" />
|
<DataDefinition :data="row" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="center" label="操作">
|
<el-table-column align="center" label="操作" width="200">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button
|
||||||
v-hasPermi="[`iot:thing-model:update`]"
|
v-hasPermi="[`iot:thing-model:update`]"
|
||||||
|
|
@ -81,6 +81,15 @@
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="showModbusCommandButton(scope.row)"
|
||||||
|
v-hasPermi="['iot:thing-model:query']"
|
||||||
|
link
|
||||||
|
type="success"
|
||||||
|
@click="openModbusCommand(scope.row)"
|
||||||
|
>
|
||||||
|
查看命令
|
||||||
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
@ -97,12 +106,15 @@
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ThingModelForm ref="formRef" @success="getList" />
|
<ThingModelForm ref="formRef" @success="getList" />
|
||||||
<ThingModelTSL ref="tslRef" />
|
<ThingModelTSL ref="tslRef" />
|
||||||
|
<!-- Modbus 命令详情对话框 -->
|
||||||
|
<ThingModelModbusCommandDialog ref="modbusCommandDialogRef" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
|
import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import ThingModelForm from './ThingModelForm.vue'
|
import ThingModelForm from './ThingModelForm.vue'
|
||||||
import ThingModelTSL from './ThingModelTSL.vue'
|
import ThingModelTSL from './ThingModelTSL.vue'
|
||||||
|
import ThingModelModbusCommandDialog from './ThingModelModbusCommandDialog.vue'
|
||||||
import { ProductVO } from '@/api/iot/product/product'
|
import { ProductVO } from '@/api/iot/product/product'
|
||||||
import { getDataTypeOptionsLabel, IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
import { getDataTypeOptionsLabel, IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
|
||||||
import { DataDefinition } from './components'
|
import { DataDefinition } from './components'
|
||||||
|
|
@ -169,6 +181,29 @@ const handleDelete = async (id: number) => {
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 判断是否显示 Modbus 命令按钮 */
|
||||||
|
const showModbusCommandButton = (row: ThingModelData) => {
|
||||||
|
// 必须是 Modbus 产品
|
||||||
|
const isModbusProduct =
|
||||||
|
product?.value?.protocolType === 'MODBUS_RTU' || product?.value?.protocolType === 'MODBUS_TCP'
|
||||||
|
if (!isModbusProduct) return false
|
||||||
|
|
||||||
|
// 必须是属性类型(type = 1)
|
||||||
|
if (row.type !== 1) return false
|
||||||
|
|
||||||
|
// 必须有 modbusConfig 且配置了寄存器地址和功能码
|
||||||
|
const config = row.property?.modbusConfig
|
||||||
|
return !!(config && config.registerAddress !== undefined && config.functionCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开 Modbus 命令详情对话框 */
|
||||||
|
const modbusCommandDialogRef = ref()
|
||||||
|
const openModbusCommand = (row: ThingModelData) => {
|
||||||
|
if (row.property?.modbusConfig && product?.value?.id) {
|
||||||
|
modbusCommandDialogRef.value?.open(row.property.modbusConfig, product.value.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,42 @@ export const THING_MODEL_GROUP_LABELS = {
|
||||||
SERVICE: '设备服务'
|
SERVICE: '设备服务'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
/** IoT Modbus 数据采集模式枚举 */
|
||||||
|
export const IoTModbusCollectionModeEnum = {
|
||||||
|
POLLING: {
|
||||||
|
label: '定时轮询',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
ACTIVE_REPORT: {
|
||||||
|
label: '主动上报',
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** 获取 Modbus 采集模式标签 */
|
||||||
|
export const getModbusCollectionModeLabel = (value: number): string => {
|
||||||
|
const mode = Object.values(IoTModbusCollectionModeEnum).find((mode) => mode.value === value)
|
||||||
|
return mode?.label || String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** IoT Modbus 字节序枚举 */
|
||||||
|
export const IoTModbusByteOrderEnum = {
|
||||||
|
BIG_ENDIAN: {
|
||||||
|
label: 'ABCD',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
LITTLE_ENDIAN: {
|
||||||
|
label: 'CDAB',
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/** 获取 Modbus 字节序标签 */
|
||||||
|
export const getModbusByteOrderLabel = (value: number): string => {
|
||||||
|
const order = Object.values(IoTModbusByteOrderEnum).find((order) => order.value === value)
|
||||||
|
return order?.label || String(value)
|
||||||
|
}
|
||||||
|
|
||||||
// IoT OTA 任务设备范围枚举
|
// IoT OTA 任务设备范围枚举
|
||||||
export const IoTOtaTaskDeviceScopeEnum = {
|
export const IoTOtaTaskDeviceScopeEnum = {
|
||||||
ALL: {
|
ALL: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue