feat:【IoT 物联网】调整设备模拟发送消息的接入
This commit is contained in:
@ -49,6 +49,7 @@ export interface DeviceHistoryDataVO {
|
|||||||
data: string // 数据
|
data: string // 数据
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @芋艿:调整到 constants
|
||||||
// IoT 设备状态枚举
|
// IoT 设备状态枚举
|
||||||
export enum DeviceStateEnum {
|
export enum DeviceStateEnum {
|
||||||
INACTIVE = 0, // 未激活
|
INACTIVE = 0, // 未激活
|
||||||
@ -56,22 +57,6 @@ export enum DeviceStateEnum {
|
|||||||
OFFLINE = 2 // 离线
|
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
|
// 设备认证参数 VO
|
||||||
export interface IotDeviceAuthInfoVO {
|
export interface IotDeviceAuthInfoVO {
|
||||||
clientId: string // 客户端 ID
|
clientId: string // 客户端 ID
|
||||||
@ -79,6 +64,13 @@ export interface IotDeviceAuthInfoVO {
|
|||||||
password: string // 密码
|
password: string // 密码
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IoT 设备发送消息 Request VO
|
||||||
|
export interface IotDeviceMessageSendReqVO {
|
||||||
|
deviceId: number // 设备编号
|
||||||
|
method: string // 请求方法
|
||||||
|
params?: any // 请求参数
|
||||||
|
}
|
||||||
|
|
||||||
// 设备 API
|
// 设备 API
|
||||||
export const DeviceApi = {
|
export const DeviceApi = {
|
||||||
// 查询设备分页
|
// 查询设备分页
|
||||||
@ -136,16 +128,6 @@ export const DeviceApi = {
|
|||||||
return await request.download({ url: `/iot/device/get-import-template` })
|
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) => {
|
getLatestDeviceProperties: async (params: any) => {
|
||||||
return await request.get({ url: `/iot/device/property/latest`, params })
|
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 })
|
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) => {
|
getDeviceAuthInfo: async (id: number) => {
|
||||||
return await request.get({ url: `/iot/device/get-auth-info`, params: { id } })
|
return await request.get({ url: `/iot/device/get-auth-info`, params: { id } })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 根据 ProductKey 和 DeviceNames 获取设备列表
|
// 根据 ProductKey 和 DeviceNames 获取设备列表
|
||||||
// TODO @puhui999:getDeviceListByProductKeyAndNames 哈。项目的风格统一~
|
// TODO @puhui999:有没可能搞成基于 id 的查询哈?
|
||||||
getDevicesByProductKeyAndNames: async (productKey: string, deviceNames: string[]) => {
|
getDevicesByProductKeyAndNames: async (productKey: string, deviceNames: string[]) => {
|
||||||
return await request.get({
|
return await request.get({
|
||||||
url: `/iot/device/list-by-product-key-and-names`,
|
url: `/iot/device/list-by-product-key-and-names`,
|
||||||
@ -176,5 +153,15 @@ export const DeviceApi = {
|
|||||||
deviceNames: deviceNames.join(',')
|
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,21 @@
|
|||||||
<el-form :model="queryParams" inline>
|
<el-form :model="queryParams" inline>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-select v-model="queryParams.method" placeholder="所有方法" class="!w-160px" clearable>
|
<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-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<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="true" />
|
||||||
<el-option label="下行" value="false" />
|
<el-option label="下行" value="false" />
|
||||||
</el-select>
|
</el-select>
|
||||||
@ -33,7 +43,6 @@
|
|||||||
|
|
||||||
<!-- 消息列表 -->
|
<!-- 消息列表 -->
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" class="whitespace-nowrap">
|
<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">
|
<el-table-column label="时间" align="center" prop="ts" width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatDate(scope.row.ts) }}
|
{{ formatDate(scope.row.ts) }}
|
||||||
@ -51,19 +60,25 @@
|
|||||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.reply" />
|
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.reply" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="请求编号" align="center" prop="requestId" width="300" />
|
||||||
<el-table-column label="请求方法" align="center" prop="method" width="140">
|
<el-table-column label="请求方法" align="center" prop="method" width="140">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ methodOptions.find(item => item.value === scope.row.method)?.label }}
|
{{ methodOptions.find((item) => item.value === scope.row.method)?.label }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="请求/响应数据" align="center" prop="params" :show-overflow-tooltip="true">
|
<el-table-column
|
||||||
<template #default="scope">
|
label="请求/响应数据"
|
||||||
<span v-if="scope.row.reply">
|
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}\}` }}
|
{{ `{"code":${scope.row.code},"msg":"${scope.row.msg}","data":${scope.row.data}\}` }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else>{{ scope.row.params }}</span>
|
<span v-else>{{ scope.row.params }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
@ -106,7 +121,7 @@ let autoRefreshTimer: any = null // TODO @super:autoRefreshEnable,autoRefres
|
|||||||
|
|
||||||
// 消息方法选项
|
// 消息方法选项
|
||||||
const methodOptions = computed(() => {
|
const methodOptions = computed(() => {
|
||||||
return Object.values(IotDeviceMessageMethodEnum).map(item => ({
|
return Object.values(IotDeviceMessageMethodEnum).map((item) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.method
|
value: item.method
|
||||||
}))
|
}))
|
||||||
@ -166,4 +181,20 @@ onMounted(() => {
|
|||||||
getMessageList()
|
getMessageList()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 刷新消息列表 */
|
||||||
|
const refresh = (delay = 0) => {
|
||||||
|
if (delay > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
handleQuery()
|
||||||
|
}, delay)
|
||||||
|
} else {
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 暴露方法给父组件 */
|
||||||
|
defineExpose({
|
||||||
|
refresh
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -41,7 +41,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
<!-- TODO @super:发送按钮,可以放在右侧哈。因为我们的 simulateValue 就在最右侧 -->
|
<!-- TODO @super:发送按钮,可以放在右侧哈。因为我们的 simulateValue 就在最右侧 -->
|
||||||
<div class="mt-10px">
|
<div class="mt-10px">
|
||||||
<el-button type="primary" @click="handlePropertyReport"> 发送</el-button>
|
<el-button type="primary" @click="handlePropertyPost"> 发送</el-button>
|
||||||
</div>
|
</div>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<!-- TODO @super:待实现 -->
|
<!-- TODO @super:待实现 -->
|
||||||
<el-tab-pane label="事件上报" name="event">
|
<el-tab-pane label="事件上报" name="event">
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- TODO @super:因为事件是每个 event 去模拟,而不是类似属性的批量上传。所以,可以每一列后面有个“模拟”按钮。另外,“值”使用 textarea,高度 3 -->
|
<!-- TODO @super:因为事件是每个 event 去模拟,而不是类似属性的批量上传。所以,可以每一列后面有个"模拟"按钮。另外,"值"使用 textarea,高度 3 -->
|
||||||
<!-- <el-table v-loading="loading" :data="eventList" :stripe="true">
|
<!-- <el-table v-loading="loading" :data="eventList" :stripe="true">
|
||||||
<el-table-column label="功能名称" align="center" prop="name" />
|
<el-table-column label="功能名称" align="center" prop="name" />
|
||||||
<el-table-column label="标识符" align="center" prop="identifier" />
|
<el-table-column label="标识符" align="center" prop="identifier" />
|
||||||
@ -132,11 +132,9 @@
|
|||||||
|
|
||||||
<!-- 右侧设备日志区域 -->
|
<!-- 右侧设备日志区域 -->
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-tabs type="border-card">
|
<ContentWrap title="设备消息">
|
||||||
<el-tab-pane label="设备日志">
|
<DeviceDetailsMessage ref="deviceMessageRef" :device-id="device.id" />
|
||||||
<DeviceDetailsMessage :device-id="device.id" />
|
</ContentWrap>
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
@ -149,6 +147,7 @@ import { DeviceApi, DeviceStateEnum, DeviceVO } from '@/api/iot/device/device'
|
|||||||
import DeviceDetailsMessage from './DeviceDetailsMessage.vue'
|
import DeviceDetailsMessage from './DeviceDetailsMessage.vue'
|
||||||
import { getDataTypeOptionsLabel } from '@/views/iot/thingmodel/config'
|
import { getDataTypeOptionsLabel } from '@/views/iot/thingmodel/config'
|
||||||
import { DataDefinition } from '@/views/iot/thingmodel/components'
|
import { DataDefinition } from '@/views/iot/thingmodel/components'
|
||||||
|
import { IotDeviceMessageMethodEnum } from '@/views/iot/utils/constants'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
product: ProductVO
|
product: ProductVO
|
||||||
@ -158,10 +157,12 @@ const props = defineProps<{
|
|||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const activeTab = ref('up') // TODO @super:upstream 上行、downstream 下行
|
const activeTab = ref('up') // TODO @super:upstream 上行、downstream 下行
|
||||||
const subTab = ref('property') // TODO @super:upstreamTab
|
const subTab = ref('property') // TODO @super:upstreamTab
|
||||||
|
const deviceMessageRef = ref() // 设备消息组件引用
|
||||||
|
const deviceMessageRefresnhDelay = 2000 // 延迟 N 秒,保证模拟上行的消息被处理
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
type: undefined, // TODO @super:type 默认给个第一个 tab 对应的,避免下面 watch 爆红
|
type: undefined as number | undefined, // TODO @super:type 默认给个第一个 tab 对应的,避免下面 watch 爆红
|
||||||
productId: -1
|
productId: -1
|
||||||
})
|
})
|
||||||
const list = ref<SimulatorData[]>([]) // 物模型列表的数据 TODO @super:thingModelList
|
const list = ref<SimulatorData[]>([]) // 物模型列表的数据 TODO @super:thingModelList
|
||||||
@ -228,9 +229,6 @@ watch(
|
|||||||
case 'event':
|
case 'event':
|
||||||
queryParams.type = 3
|
queryParams.type = 3
|
||||||
break
|
break
|
||||||
// case 'status':
|
|
||||||
// queryParams.type = 'status'
|
|
||||||
// break
|
|
||||||
}
|
}
|
||||||
} else if (newActiveTab === 'down') {
|
} else if (newActiveTab === 'down') {
|
||||||
switch (newSubTab) {
|
switch (newSubTab) {
|
||||||
@ -248,26 +246,26 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
/** 处理属性上报 */
|
/** 处理属性上报 */
|
||||||
const handlePropertyReport = async () => {
|
const handlePropertyPost = async () => {
|
||||||
// TODO @super:数据类型效验
|
// TODO @super:数据类型效验
|
||||||
const data: Record<string, object> = {}
|
const data: Record<string, any> = {}
|
||||||
list.value.forEach((item) => {
|
list.value.forEach((item) => {
|
||||||
// 只有当 simulateValue 有值时才添加到 content 中
|
// 只有当 simulateValue 有值时才添加到 content 中
|
||||||
// TODO @super:直接 if (item.simulateValue) 就可以哈,js 这块还是比较灵活的
|
// TODO @super:直接 if (item.simulateValue) 就可以哈,js 这块还是比较灵活的
|
||||||
if (item.simulateValue !== undefined && item.simulateValue !== '') {
|
if (item.simulateValue !== undefined && item.simulateValue !== '' && item.identifier) {
|
||||||
// TODO @super:这里有个红色的 idea 告警,觉得去除下
|
// TODO @super:这里有个红色的 idea 告警,觉得去除下
|
||||||
data[item.identifier] = item.simulateValue
|
data[item.identifier] = item.simulateValue
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await DeviceApi.upstreamDevice({
|
await DeviceApi.sendDeviceMessage({
|
||||||
id: props.device.id,
|
deviceId: props.device.id,
|
||||||
type: 'property',
|
method: IotDeviceMessageMethodEnum.PROPERTY_POST.method,
|
||||||
identifier: 'report',
|
params: data
|
||||||
data: data
|
|
||||||
})
|
})
|
||||||
message.success('属性上报成功')
|
message.success('属性上报成功')
|
||||||
|
deviceMessageRef.value.refresh(deviceMessageRefresnhDelay)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('属性上报失败')
|
message.error('属性上报失败')
|
||||||
}
|
}
|
||||||
@ -305,13 +303,15 @@ const handlePropertyReport = async () => {
|
|||||||
/** 处理设备状态 */
|
/** 处理设备状态 */
|
||||||
const handleDeviceState = async (state: number) => {
|
const handleDeviceState = async (state: number) => {
|
||||||
try {
|
try {
|
||||||
await DeviceApi.upstreamDevice({
|
await DeviceApi.sendDeviceMessage({
|
||||||
id: props.device.id,
|
deviceId: props.device.id,
|
||||||
type: 'state',
|
method:
|
||||||
identifier: 'report',
|
state === DeviceStateEnum.ONLINE
|
||||||
data: state
|
? IotDeviceMessageMethodEnum.STATE_ONLINE.method
|
||||||
|
: IotDeviceMessageMethodEnum.STATE_OFFLINE.method
|
||||||
})
|
})
|
||||||
message.success(`设备${state === DeviceStateEnum.ONLINE ? '上线' : '下线'}成功`)
|
message.success(`设备${state === DeviceStateEnum.ONLINE ? '上线' : '下线'}成功`)
|
||||||
|
deviceMessageRef.value.refresh(deviceMessageRefresnhDelay)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(`设备${state === DeviceStateEnum.ONLINE ? '上线' : '下线'}失败`)
|
message.error(`设备${state === DeviceStateEnum.ONLINE ? '上线' : '下线'}失败`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user