feat:【IoT 物联网】调整设备模拟发送消息的接入

This commit is contained in:
YunaiV
2025-06-14 14:37:39 +08:00
parent f17404615e
commit 6e0ba43db6
3 changed files with 86 additions and 68 deletions

View File

@ -49,6 +49,7 @@ export interface DeviceHistoryDataVO {
data: string // 数据
}
// TODO @芋艿:调整到 constants
// IoT 设备状态枚举
export enum DeviceStateEnum {
INACTIVE = 0, // 未激活
@ -56,22 +57,6 @@ export enum DeviceStateEnum {
OFFLINE = 2 // 离线
}
// IoT 设备上行 Request VO
export interface IotDeviceUpstreamReqVO {
id: number // 设备编号
type: string // 消息类型
identifier: string // 标识符
data: any // 请求参数
}
// IoT 设备下行 Request VO
export interface IotDeviceDownstreamReqVO {
id: number // 设备编号
type: string // 消息类型
identifier: string // 标识符
data: any // 请求参数
}
// 设备认证参数 VO
export interface IotDeviceAuthInfoVO {
clientId: string // 客户端 ID
@ -79,6 +64,13 @@ export interface IotDeviceAuthInfoVO {
password: string // 密码
}
// IoT 设备发送消息 Request VO
export interface IotDeviceMessageSendReqVO {
deviceId: number // 设备编号
method: string // 请求方法
params?: any // 请求参数
}
// 设备 API
export const DeviceApi = {
// 查询设备分页
@ -136,16 +128,6 @@ export const DeviceApi = {
return await request.download({ url: `/iot/device/get-import-template` })
},
// 设备上行
upstreamDevice: async (data: IotDeviceUpstreamReqVO) => {
return await request.post({ url: `/iot/device/upstream`, data })
},
// 设备下行
downstreamDevice: async (data: IotDeviceDownstreamReqVO) => {
return await request.post({ url: `/iot/device/downstream`, data })
},
// 获取设备属性最新数据
getLatestDeviceProperties: async (params: any) => {
return await request.get({ url: `/iot/device/property/latest`, params })
@ -156,18 +138,13 @@ export const DeviceApi = {
return await request.get({ url: `/iot/device/property/history-page`, params })
},
// 查询设备日志分页
getDeviceMessagePage: async (params: any) => {
return await request.get({ url: `/iot/device/message/page`, params })
},
// 获取设备 MQTT 连接参数
// 获取设备认证信息
getDeviceAuthInfo: async (id: number) => {
return await request.get({ url: `/iot/device/get-auth-info`, params: { id } })
},
// 根据 ProductKey 和 DeviceNames 获取设备列表
// TODO @puhui999getDeviceListByProductKeyAndNames 哈。项目的风格统一~
// TODO @puhui999有没可能搞成基于 id 的查询哈?
getDevicesByProductKeyAndNames: async (productKey: string, deviceNames: string[]) => {
return await request.get({
url: `/iot/device/list-by-product-key-and-names`,
@ -176,5 +153,15 @@ export const DeviceApi = {
deviceNames: deviceNames.join(',')
}
})
},
// 查询设备消息分页
getDeviceMessagePage: async (params: any) => {
return await request.get({ url: `/iot/device/message/page`, params })
},
// 发送设备消息
sendDeviceMessage: async (params: IotDeviceMessageSendReqVO) => {
return await request.post({ url: `/iot/device/message/send`, data: params })
}
}

View File

@ -5,11 +5,21 @@
<el-form :model="queryParams" inline>
<el-form-item>
<el-select v-model="queryParams.method" placeholder="所有方法" class="!w-160px" clearable>
<el-option v-for="item in methodOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-option
v-for="item in methodOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="queryParams.upstream" placeholder="上行/下行" class="!w-160px" clearable>
<el-select
v-model="queryParams.upstream"
placeholder="上行/下行"
class="!w-160px"
clearable
>
<el-option label="上行" value="true" />
<el-option label="下行" value="false" />
</el-select>
@ -33,7 +43,6 @@
<!-- 消息列表 -->
<el-table v-loading="loading" :data="list" :stripe="true" class="whitespace-nowrap">
<el-table-column label="请求编号" align="center" prop="requestId" width="300" />
<el-table-column label="时间" align="center" prop="ts" width="180">
<template #default="scope">
{{ formatDate(scope.row.ts) }}
@ -51,19 +60,25 @@
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.reply" />
</template>
</el-table-column>
<el-table-column label="请求编号" align="center" prop="requestId" width="300" />
<el-table-column label="请求方法" align="center" prop="method" width="140">
<template #default="scope">
{{ methodOptions.find(item => item.value === scope.row.method)?.label }}
{{ methodOptions.find((item) => item.value === scope.row.method)?.label }}
</template>
</el-table-column>
<el-table-column label="请求/响应数据" align="center" prop="params" :show-overflow-tooltip="true">
<template #default="scope">
<span v-if="scope.row.reply">
<el-table-column
label="请求/响应数据"
align="center"
prop="params"
:show-overflow-tooltip="true"
>
<template #default="scope">
<span v-if="scope.row.reply">
{{ `{"code":${scope.row.code},"msg":"${scope.row.msg}","data":${scope.row.data}\}` }}
</span>
<span v-else>{{ scope.row.params }}</span>
</template>
</el-table-column>
<span v-else>{{ scope.row.params }}</span>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
@ -106,7 +121,7 @@ let autoRefreshTimer: any = null // TODO @superautoRefreshEnableautoRefres
// 消息方法选项
const methodOptions = computed(() => {
return Object.values(IotDeviceMessageMethodEnum).map(item => ({
return Object.values(IotDeviceMessageMethodEnum).map((item) => ({
label: item.name,
value: item.method
}))
@ -166,4 +181,20 @@ onMounted(() => {
getMessageList()
}
})
/** 刷新消息列表 */
const refresh = (delay = 0) => {
if (delay > 0) {
setTimeout(() => {
handleQuery()
}, delay)
} else {
handleQuery()
}
}
/** 暴露方法给父组件 */
defineExpose({
refresh
})
</script>

View File

@ -41,7 +41,7 @@
</el-table>
<!-- TODO @super发送按钮可以放在右侧哈因为我们的 simulateValue 就在最右侧 -->
<div class="mt-10px">
<el-button type="primary" @click="handlePropertyReport"> 发送</el-button>
<el-button type="primary" @click="handlePropertyPost"> 发送</el-button>
</div>
</ContentWrap>
</el-tab-pane>
@ -50,7 +50,7 @@
<!-- TODO @super待实现 -->
<el-tab-pane label="事件上报" name="event">
<ContentWrap>
<!-- TODO @super因为事件是每个 event 去模拟而不是类似属性的批量上传所以可以每一列后面有个模拟按钮另外使用 textarea高度 3 -->
<!-- TODO @super因为事件是每个 event 去模拟而不是类似属性的批量上传所以可以每一列后面有个"模拟"按钮另外"值"使用 textarea高度 3 -->
<!-- <el-table v-loading="loading" :data="eventList" :stripe="true">
<el-table-column label="功能名称" align="center" prop="name" />
<el-table-column label="标识符" align="center" prop="identifier" />
@ -132,11 +132,9 @@
<!-- 右侧设备日志区域 -->
<el-col :span="12">
<el-tabs type="border-card">
<el-tab-pane label="设备日志">
<DeviceDetailsMessage :device-id="device.id" />
</el-tab-pane>
</el-tabs>
<ContentWrap title="设备消息">
<DeviceDetailsMessage ref="deviceMessageRef" :device-id="device.id" />
</ContentWrap>
</el-col>
</el-row>
</ContentWrap>
@ -149,6 +147,7 @@ import { DeviceApi, DeviceStateEnum, DeviceVO } from '@/api/iot/device/device'
import DeviceDetailsMessage from './DeviceDetailsMessage.vue'
import { getDataTypeOptionsLabel } from '@/views/iot/thingmodel/config'
import { DataDefinition } from '@/views/iot/thingmodel/components'
import { IotDeviceMessageMethodEnum } from '@/views/iot/utils/constants'
const props = defineProps<{
product: ProductVO
@ -158,10 +157,12 @@ const props = defineProps<{
const message = useMessage() // 消息弹窗
const activeTab = ref('up') // TODO @superupstream 上行、downstream 下行
const subTab = ref('property') // TODO @superupstreamTab
const deviceMessageRef = ref() // 设备消息组件引用
const deviceMessageRefresnhDelay = 2000 // 延迟 N 秒,保证模拟上行的消息被处理
const loading = ref(false)
const queryParams = reactive({
type: undefined, // TODO @supertype 默认给个第一个 tab 对应的,避免下面 watch 爆红
type: undefined as number | undefined, // TODO @supertype 默认给个第一个 tab 对应的,避免下面 watch 爆红
productId: -1
})
const list = ref<SimulatorData[]>([]) // 物模型列表的数据 TODO @superthingModelList
@ -228,9 +229,6 @@ watch(
case 'event':
queryParams.type = 3
break
// case 'status':
// queryParams.type = 'status'
// break
}
} else if (newActiveTab === 'down') {
switch (newSubTab) {
@ -248,26 +246,26 @@ watch(
)
/** 处理属性上报 */
const handlePropertyReport = async () => {
const handlePropertyPost = async () => {
// TODO @super:数据类型效验
const data: Record<string, object> = {}
const data: Record<string, any> = {}
list.value.forEach((item) => {
// 只有当 simulateValue 有值时才添加到 content 中
// TODO @super直接 if (item.simulateValue) 就可以哈js 这块还是比较灵活的
if (item.simulateValue !== undefined && item.simulateValue !== '') {
if (item.simulateValue !== undefined && item.simulateValue !== '' && item.identifier) {
// TODO @super这里有个红色的 idea 告警,觉得去除下
data[item.identifier] = item.simulateValue
}
})
try {
await DeviceApi.upstreamDevice({
id: props.device.id,
type: 'property',
identifier: 'report',
data: data
await DeviceApi.sendDeviceMessage({
deviceId: props.device.id,
method: IotDeviceMessageMethodEnum.PROPERTY_POST.method,
params: data
})
message.success('属性上报成功')
deviceMessageRef.value.refresh(deviceMessageRefresnhDelay)
} catch (error) {
message.error('属性上报失败')
}
@ -305,13 +303,15 @@ const handlePropertyReport = async () => {
/** 处理设备状态 */
const handleDeviceState = async (state: number) => {
try {
await DeviceApi.upstreamDevice({
id: props.device.id,
type: 'state',
identifier: 'report',
data: state
await DeviceApi.sendDeviceMessage({
deviceId: props.device.id,
method:
state === DeviceStateEnum.ONLINE
? IotDeviceMessageMethodEnum.STATE_ONLINE.method
: IotDeviceMessageMethodEnum.STATE_OFFLINE.method
})
message.success(`设备${state === DeviceStateEnum.ONLINE ? '上线' : '下线'}成功`)
deviceMessageRef.value.refresh(deviceMessageRefresnhDelay)
} catch (error) {
message.error(`设备${state === DeviceStateEnum.ONLINE ? '上线' : '下线'}失败`)
}