Explorar o código

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

YunaiV hai 11 meses
pai
achega
6e0ba43db6

+ 20 - 33
src/api/iot/device/device/index.ts

@@ -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 @puhui999:getDeviceListByProductKeyAndNames 哈。项目的风格统一~
+  // 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 })
   }
 }

+ 42 - 11
src/views/iot/device/device/detail/DeviceDetailsMessage.vue

@@ -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 @super:autoRefreshEnable,autoRefres
 
 // 消息方法选项
 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>

+ 24 - 24
src/views/iot/device/device/detail/DeviceDetailsSimulator.vue

@@ -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 @super:upstream 上行、downstream 下行
 const subTab = ref('property') // TODO @super:upstreamTab
+const deviceMessageRef = ref() // 设备消息组件引用
+const deviceMessageRefresnhDelay = 2000 // 延迟 N 秒,保证模拟上行的消息被处理
 
 const loading = ref(false)
 const queryParams = reactive({
-  type: undefined, // TODO @super:type 默认给个第一个 tab 对应的,避免下面 watch 爆红
+  type: undefined as number | undefined, // TODO @super:type 默认给个第一个 tab 对应的,避免下面 watch 爆红
   productId: -1
 })
 const list = ref<SimulatorData[]>([]) // 物模型列表的数据 TODO @super:thingModelList
@@ -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 ? '上线' : '下线'}失败`)
   }