Jelajahi Sumber

feat: receiving card

Casper Dai 2 tahun lalu
induk
melakukan
782f840299

+ 109 - 92
src/components/service/FullLink/index.vue

@@ -31,10 +31,11 @@
               :device="led"
               autoplay
               retry
+              keep
             />
             <i
-              class="o-link-item__warning"
-              :class="led.status"
+              v-if="led.powerOff"
+              class="o-link-item__off"
             />
           </template>
         </div>
@@ -52,6 +53,12 @@
 
 <script>
 import { ThirdPartyDevice } from '@/constant'
+import {
+  Power,
+  Status,
+  transformToPowers,
+  parsePowerSwitchStatus
+} from '@/utils/adapter/nova'
 import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
 import { getStatusReport } from '@/api/device'
 
@@ -114,7 +121,8 @@ const LineFromeTo = {
   3: [ThirdPartyDevice.SENDING_CARD, { key: ThirdPartyDevice.RECEIVING_CARD, enable: 1 }],
   4: [ThirdPartyDevice.RECEIVING_CARD, { key: 'led', enable: 1 }],
   5: ['msr', { key: ThirdPartyDevice.GATEWAY, enable: 1 }],
-  6: ['msr', { key: ThirdPartyDevice.LED_CAMERA, enable: 1 }, { key: ThirdPartyDevice.TRAFFIC_CAMERA, enable: 1 }],
+  6: ['msr', { key: ThirdPartyDevice.LED_CAMERA, enable: 1 }],
+  15: ['msr', { key: ThirdPartyDevice.TRAFFIC_CAMERA, enable: 1 }],
   7: ['msr', { key: ThirdPartyDevice.TRAFFIC_CAMERA, enable: 1 }],
   8: ['msr', { key: ThirdPartyDevice.TRAFFIC_CAMERA, enable: 1 }],
   9: ['msr', { key: ThirdPartyDevice.LED_CAMERA, enable: 1 }],
@@ -175,38 +183,37 @@ export default {
         return {
           key, label, status, canClick,
           className: [
-            status === -2
+            status === Status.NONE
               ? 'u-hidden'
-              : status === -1
+              : status === Status.LOADING
                 ? 'loading'
-                : status === 0
+                : status === Status.ERROR
                   ? 'tip el-icon-error u-color--error'
-                  : status === 1
+                  : status === Status.OK
                     ? ''
                     : 'tip el-icon-warning u-color--warning',
             alias || key,
-            canClick && status !== -2 ? 'u-pointer' : ''
+            canClick && status !== Status.NONE ? 'u-pointer' : ''
           ].join(' ')
         }
       })
     },
     lines () {
-      console.log('line', this.linkDeviceMap)
       const state = this.linkState
       return Object.keys(LineFromeTo).map(key => {
         const from = LineFromeTo[key][0]
         const to = LineFromeTo[key].slice(1)
-        const enable = state[from] != null && state[from] > -2 && to.some(
-          ({ key, enable }) => enable && state[key] != null && state[key] > -2
+        const enable = state[from] > Status.NONE && !to.some(
+          ({ key, enable }) => enable && state[key] === Status.NONE
         ) && !to.some(
-          ({ key, enable }) => !enable && state[key] != null && state[key] > -2
+          ({ key, enable }) => !enable && state[key] > Status.NONE
         )
         return {
           key: `line${key}`,
           className: [
             `l${key}`,
             enable
-              ? state[from] > 0 && to.some(({ key, enable }) => enable && state[key] > 0)
+              ? this.hasLink(state[from]) && !to.some(({ key, enable }) => enable && !this.hasLink(state[key]))
                 ? 'linked'
                 : ''
               : 'u-hidden'
@@ -239,17 +246,25 @@ export default {
     clearInterval(this.$timer)
   },
   methods: {
+    hasLink (status) {
+      return status > Status.LOADING && status !== Status.ERROR
+    },
     onMessage (value) {
-      console.log('power', value)
-      this.$powerStatus = value[ThirdPartyDevice.MULTI_FUNCTION_CARD]
-      if (this.linkDeviceMap && this.$powerStatus.length) {
-        const switchStatus = this.$powerStatus[0].switchStatus
-        this.linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD] = switchStatus === 255 ? 0 : 1
-        this.led.status = switchStatus === 255 ? 'off' : switchStatus === 0 ? 'on' : 'half'
+      console.log('full link', value)
+      this.$receiverStatus = value[ThirdPartyDevice.RECEIVING_CARD].status
+      const powerStatus = value[ThirdPartyDevice.MULTI_FUNCTION_CARD]
+      if (powerStatus.length && powerStatus.switchStatus > Status.LOADING) {
+        this.$powerStatus = powerStatus
       }
-      this.$receiverStatus = value[ThirdPartyDevice.RECEIVING_CARD]?.status
-      if (this.linkDeviceMap && this.$receiverStatus >= 0) {
-        this.linkDeviceMap[ThirdPartyDevice.RECEIVING_CARD] = this.$receiverStatus
+      if (this.linkDeviceMap) {
+        if (this.linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD] > Status.NONE && powerStatus.length) {
+          const switchStatus = powerStatus[0].switchStatus
+          this.linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD] = switchStatus === Power.ON ? Status.OK : switchStatus === Power.OFF ? Status.ERROR : Status.WARNING
+        }
+        if (this.linkDeviceMap[ThirdPartyDevice.RECEIVING_CARD] > Status.NONE && this.$receiverStatus >= Status.OK) {
+          this.linkDeviceMap[ThirdPartyDevice.RECEIVING_CARD] = this.$receiverStatus
+        }
+        this.onUpdateLed()
       }
     },
     checkScale () {
@@ -281,61 +296,57 @@ export default {
           ThirdPartyDevice.BOX,
           ThirdPartyDevice.MULTI_FUNCTION_CARD
         ], { custom: true }),
-        this.$powerStatus
-          ? Promise.resolve({ data: this.$powerStatus })
-          : getStatusReport([this.targetId])
+        getStatusReport([this.targetId])
       ]).then(([{ data: nodes }, { data }]) => {
         this.$deviceTypes = []
-        const map = {
-          msr: 1,
-          led: 0,
-          [ThirdPartyDevice.BOX]: -2,
-          [ThirdPartyDevice.GATEWAY]: -2,
-          [ThirdPartyDevice.LED_CAMERA]: -2,
-          [ThirdPartyDevice.TRAFFIC_CAMERA]: -2,
-          [ThirdPartyDevice.SENDING_CARD]: -2,
-          [ThirdPartyDevice.RECEIVING_CARD]: -2,
-          [ThirdPartyDevice.MULTI_FUNCTION_CARD]: -2
-        }
+        const linkDeviceMap = {}
         const by = [ThirdPartyDevice.BOX]
-        let statusKey = ''
         nodes.forEach(({ nodeType, instance }) => {
           if (nodeType === ThirdPartyDevice.RECEIVING_CARD) {
-            // 需对接接收卡
-            map[nodeType] = this.$receiverStatus ?? -1
-            by.push(ThirdPartyDevice.RECEIVING_CARD)
+            linkDeviceMap[nodeType] = this.$receiverStatus ?? Status.LOADING
+            by.push(nodeType)
           } else if (nodeType === ThirdPartyDevice.MULTI_FUNCTION_CARD) {
             if (instance) {
-              by.push(ThirdPartyDevice.MULTI_FUNCTION_CARD)
-              if (data[0] && data[0].switchStatus >= 0) {
-                map[ThirdPartyDevice.MULTI_FUNCTION_CARD] = data[0].switchStatus === 255 ? 0 : 1
-                statusKey = data[0].switchStatus === 255 ? 'off' : data[0].switchStatus === 0 ? 'on' : 'half'
+              if (data[0] && data[0].switchStatus > Power.LOADING) {
+                const switchStatus = parsePowerSwitchStatus(transformToPowers(data[0].switchStatus, JSON.parse(data[0].typeArray)))
+                linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD] = switchStatus === Power.OFF ? Status.ERROR : Status.OK
               } else {
-                map[ThirdPartyDevice.MULTI_FUNCTION_CARD] = -1
+                linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD] = Status.LOADING
               }
             }
           } else if (nodeType === ThirdPartyDevice.SENDING_CARD) {
-            map[nodeType] = 1
+            linkDeviceMap[nodeType] = Status.OK
           } else if (requiredMap[nodeType]) {
             if (instance) {
-              const onlineStatus = instance.onlineStatus === 1 ? 1 : 0
-              if (map[nodeType] >= 0 && map[nodeType] !== onlineStatus) {
-                map[nodeType] = 2
+              const status = instance.onlineStatus === 1 ? Status.OK : Status.ERROR
+              if (linkDeviceMap[nodeType] > Status.LOADING && linkDeviceMap[nodeType] !== status) {
+                linkDeviceMap[nodeType] = Status.WARNING
               } else {
-                map[nodeType] = onlineStatus
+                linkDeviceMap[nodeType] = status
               }
               this.$deviceTypes.push(nodeType)
             }
           }
         })
+        this.linkDeviceMap = {
+          msr: Status.OK,
+          led: Status.ERROR,
+          [ThirdPartyDevice.BOX]: Status.NONE,
+          [ThirdPartyDevice.GATEWAY]: Status.NONE,
+          [ThirdPartyDevice.LED_CAMERA]: Status.NONE,
+          [ThirdPartyDevice.TRAFFIC_CAMERA]: Status.NONE,
+          [ThirdPartyDevice.SENDING_CARD]: Status.NONE,
+          [ThirdPartyDevice.RECEIVING_CARD]: Status.NONE,
+          [ThirdPartyDevice.MULTI_FUNCTION_CARD]: Status.NONE,
+          ...linkDeviceMap
+        }
         this.led = {
           id: this.targetId,
-          onlineStatus: by.reduce((curr, key) => Math.min(curr, map[key]), 1),
-          status: statusKey,
+          onlineStatus: 0,
+          powerOff: true,
           by
         }
-        map.led = this.led.onlineStatus
-        this.linkDeviceMap = map
+        this.onUpdateLed()
         this.loaded = true
       }).finally(() => {
         this.loading = false
@@ -352,36 +363,49 @@ export default {
       }
       Promise.all([
         getThirdPartyDevicesByThirdPartyDevice(this.targetId, this.$deviceTypes, { custom: true }),
-        this.linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD] === -2
+        this.linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD] === Status.NONE
           ? Promise.resolve({ data: [] })
           : this.$powerStatus
             ? Promise.resolve({ data: this.$powerStatus })
             : getStatusReport([this.targetId])
       ]).then(([{ data: nodes }, { data }]) => {
-        const map = {}
+        const linkDeviceMap = {}
         nodes.forEach(({ nodeType, instance }) => {
-          const onlineStatus = instance.onlineStatus === 1 ? 1 : 0
-          if (map[nodeType] >= 0 && map[nodeType] !== onlineStatus) {
-            map[nodeType] = 2
+          const onlineStatus = instance.onlineStatus === 1 ? Status.OK : Status.ERROR
+          if (linkDeviceMap[nodeType] > Status.LOADING && linkDeviceMap[nodeType] !== onlineStatus) {
+            linkDeviceMap[nodeType] = Status.WARNING
           } else {
-            map[nodeType] = onlineStatus
+            linkDeviceMap[nodeType] = onlineStatus
           }
         })
-        if (data.length && data[0].switchStatus >= 0) {
-          map[ThirdPartyDevice.MULTI_FUNCTION_CARD] = data[0].switchStatus === 255 ? 0 : 1
-          this.led.label = map[ThirdPartyDevice.MULTI_FUNCTION_CARD] ? '' : '电源未开启'
+        if (data.length) {
+          const switchStatus = data[0].switchStatus
+          if (switchStatus > Power.LOADING) {
+            linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD] = switchStatus === Power.OFF ? Status.ERROR : Status.OK
+          }
         }
-        const linkDeviceMap = {
+        this.linkDeviceMap = {
           ...this.linkDeviceMap,
-          ...map
+          ...linkDeviceMap
         }
-        linkDeviceMap.led = this.led.by.reduce((curr, key) => Math.min(curr, linkDeviceMap[key]), 1)
-        this.led.onlineStatus = linkDeviceMap.led
-        this.linkDeviceMap = linkDeviceMap
+        this.onUpdateLed()
       }).finally(() => {
         this.loading = false
       })
     },
+    onUpdateLed () {
+      const linkDeviceMap = this.linkDeviceMap
+      const by = this.led.by
+      const mutliStatus = this.linkDeviceMap[ThirdPartyDevice.MULTI_FUNCTION_CARD]
+      const hasPower = mutliStatus === Status.NONE || mutliStatus > Status.LOADING && mutliStatus !== Status.ERROR
+      this.led.onlineStatus = hasPower && !by.some(key => linkDeviceMap[key] !== Status.OK && linkDeviceMap[key] !== Status.WARNING) ? 1 : 0
+      this.led.powerOff = !hasPower
+      this.linkDeviceMap.led = this.led.onlineStatus
+        ? by.some(key => linkDeviceMap[key] !== Status.OK)
+          ? Status.WARNING
+          : Status.OK
+        : Status.ERROR
+    },
     onClick (item) {
       if (!item.canClick) {
         return
@@ -503,7 +527,8 @@ export default {
         transform: rotate(90deg);
       }
 
-      &.l6 {
+      &.l6,
+      &.l15 {
         top: 334px;
         left: 122px;
         width: 120px;
@@ -633,10 +658,12 @@ export default {
   &.loading::after {
     content: "\e6cf";
     position: absolute;
-    top: 42%;
-    left: 46%;
+    top: calc(50% - $font-size--3xl / 2);
+    left: calc(50% - $font-size--3xl / 2);
+    width: $font-size--3xl;
+    height: $font-size--3xl;
     color: #fff;
-    font-size: 32px;
+    font-size: $font-size--3xl;
     font-family: element-icons !important;
     animation: rotating 2s linear infinite;
   }
@@ -659,29 +686,14 @@ export default {
     }
   }
 
-  &__warning {
+  &__off {
     position: absolute;
     top: 6px;
     right: 8px;
+    width: 24px;
+    height: 24px;
+    background: url("~@/assets/icon_off.svg") 0 0 / 100% 100% no-repeat;
     z-index: 9;
-
-    &.on {
-      width: 24px;
-      height: 24px;
-      background: url("~@/assets/icon_on.svg") 0 0 / 100% 100% no-repeat;
-    }
-
-    &.off {
-      width: 24px;
-      height: 24px;
-      background: url("~@/assets/icon_off.svg") 0 0 / 100% 100% no-repeat;
-    }
-
-    &.half {
-      width: 54px;
-      height: 24px;
-      background: url("~@/assets/icon_on_2.svg") 0 0 / 100% 100% no-repeat;
-    }
   }
 }
 
@@ -728,10 +740,15 @@ export default {
     transform-origin: left;
   }
 
-  &.l6 {
+  &.l6,
+  &.l15 {
     @include getPosition(230px, 208px, 200px, 2px);
   }
 
+  &.l6.linked ~ &.l15 {
+    display: none;
+  }
+
   &.l7 {
     @include getPosition(428px, 208px, 96px, 2px);
     transform: rotate(90deg);

+ 2 - 0
src/store/modules/user.js

@@ -7,6 +7,7 @@ import {
   inst,
   close
 } from '@/utils/mqtt'
+import { startMonitor } from '@/utils/adapter'
 import { getTenantsByQuery } from '@/api/user'
 
 const state = {
@@ -142,6 +143,7 @@ const actions = {
         commit('SET_ORG_KEY', org || tenant[0])
       }
       inst()
+      startMonitor()
     }
   },
   updateUser ({ commit }, keycloak) {

+ 100 - 2
src/utils/adapter/index.js

@@ -1,8 +1,24 @@
+import { ThirdPartyDevice } from '@/constant'
 import {
   WillTopic,
   createClient,
   decodePayload
 } from '@/utils/mqtt'
+import {
+  Power,
+  Status,
+  GET_POWER_STATUS,
+  GET_RECEIVER_TOPOLOGY,
+  GET_RECEIVER_INFO,
+  getPowerStatusByMessage,
+  getReceivingCardTopologyByMessage,
+  getReceivingCardStatusByMessage
+} from './nova'
+
+export * from './nova'
+
+const deviceCache = new Map()
+const cbMap = new Map()
 
 let client = null
 export function startMonitor () {
@@ -44,7 +60,89 @@ export function startMonitor () {
 
   client.on('message', (topic, payload) => {
     console.log('Monitor topic', topic)
-    const message = decodePayload(topic, payload)
-    console.log(message)
+    const result = /^(\d+)\/(\d+)\/multifunctionCard\/invoke\/reply$/.exec(topic)
+    if (!result) {
+      return
+    }
+    const id = result[2]
+    console.log('monitor cache', id)
+    const message = JSON.parse(decodePayload(topic, payload))
+    const cache = getCacheById(id)
+
+    if (message.function === GET_POWER_STATUS) {
+      const data = getPowerStatusByMessage(message)
+      if (data.success) {
+        cache[ThirdPartyDevice.MULTI_FUNCTION_CARD] = data.data
+      } else {
+        return
+      }
+    } else if (message.function === GET_RECEIVER_INFO) {
+      const data = getReceivingCardStatusByMessage(message)
+      if (data.success) {
+        cache[ThirdPartyDevice.RECEIVING_CARD] = data.data
+      } else {
+        return
+      }
+    } else if (message.function === GET_RECEIVER_TOPOLOGY) {
+      const data = getReceivingCardTopologyByMessage(message)
+      if (data.success) {
+        cache.topology = data.data
+      } else {
+        return
+      }
+    } else {
+      return
+    }
+
+    cbMap.get(id)?.forEach(cb => {
+      cb(cache)
+    })
   })
+
+  client.subscribe([
+    '+/+/multifunctionCard/invoke/reply'
+  ])
+}
+
+export function getCacheById (id) {
+  let cache = deviceCache.get(id)
+  if (!cache) {
+    deviceCache.set(id, cache = {
+      [ThirdPartyDevice.MULTI_FUNCTION_CARD]: [{
+        switchStatus: Power.NONE,
+        powers: []
+      }],
+      [ThirdPartyDevice.RECEIVING_CARD]: {
+        status: Status.LOADING,
+        receivers: []
+      },
+      topology: null
+    })
+  }
+  return cache
+}
+
+export function addListener (id, cb) {
+  console.log('monitor add', id)
+  let cbSet = cbMap.get(id)
+  if (!cbSet) {
+    cbMap.set(id, cbSet = new Set())
+  }
+  if (cbSet.has(cb)) {
+    console.log('monitor', id, '->', 'exsit')
+    return
+  }
+  console.log('monitor', id, '->', 'success')
+  cbSet.add(cb)
+  cb(getCacheById(id))
+}
+
+export function removeListener (id, cb) {
+  console.log('monitor remove', id)
+  const cbSet = cbMap.get(id)
+  if (!cbSet.has(cb)) {
+    console.log('monitor', id, '->', 'not exsit')
+  }
+  console.log('monitor remove', id, '->', 'success')
+  cbSet.delete(cb)
 }

+ 130 - 30
src/utils/adapter/nova.js

@@ -1,8 +1,27 @@
+export const TOPIC_KEY = 'multifunctionCard/invoke'
+export const REPLY_TOPIC_KEY = 'multifunctionCard/invoke/reply'
+
+export const Status = {
+  NONE: -2,
+  LOADING: -1,
+  OK: 0,
+  ERROR: 1,
+  WARNING: 2
+}
+
+export const Power = {
+  NONE: -1,
+  ON: 0,
+  OFF: 255
+}
+
 // 多功能卡处理
 // 针对自实行的如9520设备,电源上报类型均为默认
 // nova自身支持类型配置,此时需固定类型,例如屏电源统一为【屏体电源】
 export const RELAY_KEY = -1
 
+export const RELEASE_MULTI = 'ReleaseLinkMultiFunctionCard' // 释放占用
+
 export const GET_POWER_STATUS = 'GetRealtimePowerSwitchStatusAsync' // 9.2.2.1、获取电源实时状态获取
 export const SET_POWER_STATUS = 'SetManualPowerSwitchStatusAsync' // 9.2.3.2、设置多功能卡电源开关状态
 export const GET_POWER_TIMING = 'GetTimingPowerSwitchStatusAsync' // 9.2.1.2、获取电源定时控制任务
@@ -21,10 +40,7 @@ export const GET_POWER_MODE = 'GetPowerModeAsync' // 9.29.2.2、获取终端电
 export function getPowerStatusByMessage (message) {
   const data = checkMessage(message)
   if (!data.success) {
-    return {
-      success: true,
-      data: data.data
-    }
+    return data
   }
   return {
     success: true,
@@ -32,34 +48,99 @@ export function getPowerStatusByMessage (message) {
   }
 }
 
+export function transformToPowers (switchStatus, typeArray) {
+  return switchStatus.toString(2).split('').map((val, index) => {
+    return {
+      type: typeArray[index],
+      action: Number(val)
+    }
+  })
+}
+
+export function parsePowerSwitchStatus (powers) {
+  const open = !powers.some(({ action, type }) => action === 1 && type !== '风机电源')
+  const closed = !powers.some(({ action, type }) => action === 0 && type !== '风机电源')
+  return closed
+    ? Power.OFF
+    : open
+      ? Power.ON
+      : parseInt(powers.map(({ action }) => action).join(''), 2)
+}
+
 export function getPowerStatus ({ current_status_info }) {
   console.log('nova power', current_status_info)
-  return current_status_info.filter(({ portIndex }) => portIndex !== -1).map(({ updatePowerIndexStates }) => {
-    const open = !updatePowerIndexStates.some(({ action, type }) => action === 1 && type !== '风机电源')
-    const closed = !updatePowerIndexStates.some(({ action, type }) => action === 0 && type !== '风机电源')
+  const powers = current_status_info.filter(({ portIndex }) => portIndex !== RELAY_KEY).map(({ portIndex, connectIndex, updatePowerIndexStates }) => {
     return {
-      switchStatus: closed
-        ? 255
-        : open
-          ? 0
-          : parseInt(updatePowerIndexStates.map(({ action }) => action).join(''), 2),
-      powers: updatePowerIndexStates.map(({ portIndex, type, action }) => {
-        return { portIndex, type, action }
+      switchStatus: parsePowerSwitchStatus(updatePowerIndexStates),
+      powers: updatePowerIndexStates.map(({ powerIndex, type, action }) => {
+        return { portIndex, connectIndex, powerIndex, type, action }
       })
     }
   })
+  return powers.length
+    ? powers
+    : [{
+      switchStatus: Power.OFF,
+      powers: []
+    }]
 }
 
 // 接收卡处理
+export const GET_RECEIVER_TOPOLOGY = 'GetReceiverCountAndInfoAsync' // 接收卡拓扑信息
+export const GET_RECEIVER_TOPOLOGY_CACHE = 'GetReceiverCountAndInfoCache' // 接收卡拓扑信息缓存
 export const GET_RECEIVER_INFO = 'GetMonitorInfoByReceiverIndexAsync' // 接收卡信息
 
-export function getReceivingCardStatusByMessage (message) {
+export function triggerReceivingCardTopologyReporting (sender, device) {
+  const { productId, id, serialNumber } = device
+  return send(sender, `${productId}/${id}/${TOPIC_KEY}`, GET_RECEIVER_TOPOLOGY, { sn: serialNumber })
+}
+
+export function getReceivingCardTopologyByMessage (message) {
   const data = checkMessage(message)
   if (!data.success) {
-    return {
-      success: true,
-      data: data.data
+    return data
+  }
+  return {
+    success: true,
+    data: getReceivingCardTopology(data.data)
+  }
+}
+
+export function getReceivingCardTopology ({ receiveCardRegionInfo }) {
+  console.log('nova topology', receiveCardRegionInfo)
+  const receiverMap = {}
+  const topology = receiveCardRegionInfo.map(({ portIndex, connectIndex, rowIndexInScreen, colIndexInScreen }) => {
+    const position = {
+      rowIndex: rowIndexInScreen,
+      colIndex: colIndexInScreen
     }
+    receiverMap[`${portIndex}-${connectIndex}`] = position
+    return { portIndex, connectIndex, position }
+  })
+  return {
+    topology,
+    receiverMap
+  }
+}
+
+export function triggerReceivingCardInfoReporting (sender, device) {
+  const { productId, id, serialNumber } = device
+  return send(sender, `${productId}/${id}/${TOPIC_KEY}`, GET_RECEIVER_INFO, {
+    sn: serialNumber,
+    info: {
+      receiveCardRegionInfo: [
+        {
+          portIndex: 0, connectIndex: 0
+        }
+      ]
+    }
+  })
+}
+
+export function getReceivingCardStatusByMessage (message) {
+  const data = checkMessage(message)
+  if (!data.success) {
+    return data
   }
   return {
     success: true,
@@ -68,25 +149,30 @@ export function getReceivingCardStatusByMessage (message) {
 }
 
 export function getReceivingCardStatus ({ screenMonitorData }) {
-  console.log('nova receiving card', screenMonitorData)
   let hasOpen = 0
   let hasClosed = 0
-  const receivers = screenMonitorData.map(({ receiveCardMonitorInfo: { connectIndex, portIndex, voltage, deviceMapList }, monitorCardMonitorInfo }) => {
-    if (voltage <= 0) {
+  let hasWarning = 0
+  const receivers = screenMonitorData.map(({ receiveCardMonitorInfo: { portIndex, connectIndex, deviceWorkState } }) => {
+    if (deviceWorkState === 0) {
+      hasOpen = 1
+    } else if (deviceWorkState === 1) {
       hasClosed = 1
     } else {
-      hasOpen = 1
-    }
-    return {
-      monitorCardMonitorInfo,
-      connectIndex, portIndex, voltage,
-      deviceMapList: deviceMapList.map(({ deviceIndex, deviceType }) => {
-        return { deviceIndex, deviceType }
-      })
+      hasWarning = 1
     }
+    return { connectIndex, portIndex, deviceWorkState }
   })
+  console.log('nova topology info', receivers)
   return {
-    status: hasOpen ? hasClosed ? 2 : 1 : 0,
+    status: receivers.length
+      ? hasWarning
+        ? Status.WARNING
+        : hasOpen
+          ? hasClosed
+            ? Status.WARNING
+            : Status.OK
+          : Status.ERROR
+      : Status.ERROR,
     receivers
   }
 }
@@ -131,3 +217,17 @@ function parseMessage (message) {
     data: message.data ? JSON.parse(message.data.replaceAll("'", '"')) : null
   }
 }
+
+function send (sender, topic, type, params) {
+  const timestamp = `${Date.now()}_${Math.random().toString(16).slice(2)}`
+  return sender(
+    topic,
+    JSON.stringify({
+      messageId: timestamp,
+      timestamp,
+      'function': type,
+      inputs: JSON.stringify(params)
+    }),
+    true
+  )
+}

+ 52 - 47
src/views/device/detail/components/DeviceExternal/components/ReceivingCard/index.vue

@@ -1,18 +1,23 @@
 <template>
-  <div
-    v-loading="!cards.length"
-    class="c-receiving-card"
-    :style="style"
-  >
+  <div class="l-flex--col">
     <div
-      v-for="card in cards"
-      :key="card.key"
-      class="o-receiving-card"
-      :class="card.status"
-      :style="card.style"
+      v-loading="!cards.length"
+      class="l-flex__auto c-sibling-item--v u-overflow--auto"
     >
-      <div class="o-receiving-card__status">
-        {{ card.tip }}
+      <div
+        v-loading="!cards.length"
+        class="c-receiving-card"
+        :style="style"
+      >
+        <div
+          v-for="card in cards"
+          :key="card.key"
+          class="o-receiving-card"
+          :class="card.status"
+          :style="card.style"
+        >
+          <i class="o-receiving-card__status" />
+        </div>
       </div>
     </div>
   </div>
@@ -20,13 +25,21 @@
 
 <script>
 import { ThirdPartyDevice } from '@/constant'
+import { publish } from '@/utils/mqtt'
 import {
   addListener,
   removeListener
-} from '../../../../monitor'
+} from '@/utils/adapter'
+import { triggerReceivingCardTopologyReporting } from '@/utils/adapter/nova'
 
 export default {
   name: 'DeviceReceivingCard',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    }
+  },
   data () {
     return {
       cards: [],
@@ -34,31 +47,40 @@ export default {
     }
   },
   created () {
-    addListener('power', this.onMessage)
+    addListener(this.device.id, this.onMessage)
   },
   beforeDestroy () {
-    removeListener('power', this.onMessage)
+    removeListener(this.device.id, this.onMessage)
   },
   methods: {
     onMessage (value) {
-      console.log(value)
-      this.setDefaults(value[ThirdPartyDevice.RECEIVING_CARD].receivers)
+      console.log('monitor topology', value)
+      this.setDefaults(value.topology, value[ThirdPartyDevice.RECEIVING_CARD].receivers)
     },
-    setDefaults (receivers) {
+    setDefaults (topology, receivers) {
+      console.log('topology receivers', topology, receivers)
+      if (!topology) {
+        this.onTrigger()
+        return
+      }
       let maxRow = 0
       let maxCol = 0
       const cards = []
-      receivers.forEach(({ connectIndex, portIndex, voltage }) => {
-        maxRow = Math.max(maxRow, connectIndex)
-        maxCol = Math.max(maxCol, portIndex)
+      const status = {}
+      receivers.forEach(({ portIndex, connectIndex, deviceWorkState }) => {
+        status[`${portIndex}-${connectIndex}`] = deviceWorkState
+      })
+      Object.keys(topology.receiverMap).forEach(key => {
+        const { rowIndex, colIndex } = topology.receiverMap[key]
+        maxRow = Math.max(maxRow, rowIndex)
+        maxCol = Math.max(maxCol, colIndex)
         const card = {
-          key: `${connectIndex}-${portIndex}`,
+          key,
           style: {
-            gridRow: connectIndex + 1,
-            gridColumn: portIndex + 1
+            gridRow: rowIndex + 1,
+            gridColumn: colIndex + 1
           },
-          status: voltage > 0 ? 'online' : 'error',
-          tip: voltage > 0 ? '在线' : '离线'
+          status: ['online', 'error', 'warning'][status[key] == null ? (receivers.length ? 1 : 2) : status[key]]
         }
         cards.push(card)
       })
@@ -67,6 +89,9 @@ export default {
         gridTemplateRows: `repeat(${maxRow + 1}, 96px)`,
         gridTemplateColumns: `repeat(${maxCol + 1}, 96px)`
       }
+    },
+    onTrigger () {
+      triggerReceivingCardTopologyReporting(publish, this.device)
     }
   }
 }
@@ -87,35 +112,15 @@ export default {
   color: $info;
   background: url("~@/assets/screen.png") 0 0 / 100% 100% no-repeat;
 
-  &.online,
-  &.error {
-    color: $success--dark;
-  }
-
-  &.online::after {
-    background-color: $black;
-  }
-
-  &.error::after {
-    background-color: $error;
-  }
-
   &__status {
-    display: inline-flex;
-    justify-content: center;
-    align-items: center;
+    display: inline-block;
     width: 36px;
     height: 36px;
-    padding-bottom: 4px;
-    color: $black;
-    font-size: 14px;
-    font-weight: bold;
     background: url("~@/assets/icon_card_unknown.png") 0 0 / 100% 100% no-repeat;
   }
 
   &.online {
     .o-receiving-card__status {
-      color: $error;
       background-image: url("~@/assets/icon_card_normal.png");
     }
   }

+ 3 - 3
src/views/device/detail/components/DeviceExternal/index.vue

@@ -28,7 +28,7 @@ import { ThirdPartyDevice } from '@/constant'
 import {
   addListener,
   removeListener
-} from '../../monitor'
+} from '@/utils/adapter'
 import SendingCard from './components/SendingCard.vue'
 import ReceivingCard from './components/ReceivingCard'
 import LedCamera from './components/LED.vue'
@@ -59,10 +59,10 @@ export default {
   },
   methods: {
     addListener (cb) {
-      addListener('power', cb)
+      addListener(this.device.id, cb)
     },
     removeListener (cb) {
-      removeListener('power', cb)
+      removeListener(this.device.id, cb)
     },
     onClick ({ key, label }) {
       let activeComponent = null

+ 63 - 10
src/views/device/detail/components/DeviceInfo/components/Power.vue

@@ -2,10 +2,10 @@
   <div>
     <div class="l-flex--row c-sibling-item--v u-color--black u-font-size--sm u-bold">
       <div class="c-sibling-item">状态</div>
-      <i
+      <!-- <i
         class="c-sibling-item el-icon-refresh u-font-size has-active"
         @click="onRefresh"
-      />
+      /> -->
     </div>
     <schema-table
       ref="powerTable"
@@ -13,7 +13,13 @@
       :schema="powerSchema"
     />
     <template v-if="hasMulti">
-      <div class="c-sibling-item--v u-color--black u-font-size--sm u-bold">定时任务</div>
+      <div class="l-flex--row c-sibling-item--v u-color--black u-font-size--sm u-bold">
+        <div class="c-sibling-item">定时任务</div>
+        <i
+          class="c-sibling-item el-icon-refresh u-font-size has-active"
+          @click="onRefresh"
+        />
+      </div>
       <schema-table
         ref="multiTable"
         class="c-sibling-item--v near"
@@ -32,7 +38,13 @@
 </template>
 
 <script>
+import { ThirdPartyDevice } from '@/constant'
+import {
+  addListener as adapterAddListener,
+  removeListener as adapterRemoveListener
+} from '@/utils/adapter'
 import {
+  Power,
   RELAY_KEY,
   GET_POWER_STATUS,
   GET_MULTI_POWER_TIMING,
@@ -71,9 +83,10 @@ export default {
       hasRelay: false,
       powerSchema: {
         singlePage: true,
-        list: this.getPowers,
+        // list: this.getPowers,
+        list: this.getCachePowers,
         cols: [
-          { label: '设备', render: ({ portIndex }) => portIndex === RELAY_KEY ? '播控盒' : `控电设备${portIndex}` },
+          { label: '设备', render: ({ portIndex }) => portIndex === RELAY_KEY ? '播控盒' : '控电设备'/* `控电设备${portIndex}` */ },
           { prop: 'powerIndex', label: '电源端口' },
           { prop: 'type', label: '电源类型' },
           { type: 'tag', render: ({ action }) => action
@@ -130,23 +143,63 @@ export default {
     }
   },
   created () {
-    addListener('multifunction', this.onMessage)
     this.$tableData = {}
     this.$tableStatus = {}
+    addListener('multifunction', this.onMessage)
+    adapterAddListener(this.device.id, this.onPowerMessage)
   },
   beforeDestroy () {
     removeListener('multifunction', this.onMessage)
+    adapterRemoveListener(this.device.id, this.onPowerMessage)
+    clearInterval(this.$cacheTimer)
     Object.keys(this.$tableStatus).forEach(key => {
       clearTimeout(this.$tableStatus[key].timer)
     })
   },
   methods: {
+    getCachePowers () {
+      if (this.$tableData[GET_POWER_STATUS]) {
+        return Promise.resolve({
+          data: this.$tableData[GET_POWER_STATUS] || []
+        })
+      }
+      return new Promise(resolve => {
+        this.$cacheTimer = setInterval(() => {
+          if (this.$tableData[GET_POWER_STATUS]) {
+            clearInterval(this.$cacheTimer)
+            resolve({ data: this.$tableData[GET_POWER_STATUS] || [] })
+          }
+        }, 1000)
+      })
+    },
+    onPowerMessage (value) {
+      if (value[ThirdPartyDevice.MULTI_FUNCTION_CARD]?.[0]?.switchStatus > Power.NONE) {
+        this.$tableData[GET_POWER_STATUS] = value[ThirdPartyDevice.MULTI_FUNCTION_CARD][0].powers || []
+        const map = {}
+        this.$tableData[GET_POWER_STATUS].forEach(({ portIndex, connectIndex, type }) => {
+          const key = `${portIndex}_${connectIndex}_${type}`
+          if (!map[key]) {
+            map[key] = {
+              portIndex,
+              connectIndex,
+              type,
+              label: portIndex === RELAY_KEY ? '' : `${portIndex} ${type}`
+            }
+          }
+        })
+        this.$typeMap = map
+        this.hasMulti = this.$tableData[GET_POWER_STATUS].length
+        this.$refs.powerTable?.pageTo()
+      }
+    },
     onRefresh () {
-      this.$tableData = {}
-      this.$tableStatus = {}
+      this.$tableData[GET_MULTI_POWER_TIMING] = null
       this.hasMulti = false
-      this.hasRelay = false
-      this.$refs.powerTable.pageTo()
+      // this.$tableData = {}
+      // this.$tableStatus = {}
+      // this.hasMulti = false
+      // this.hasRelay = false
+      // this.$refs.powerTable.pageTo()
     },
     sendTopic (invoke, inputs) {
       console.log('invoke', invoke, inputs)

+ 4 - 10
src/views/device/detail/components/DeviceInvoke/MultifunctionCardPowerSwitch.vue

@@ -97,6 +97,7 @@
 <script>
 import { mapGetters } from 'vuex'
 import {
+  RELEASE_MULTI,
   GET_POWER_STATUS,
   SET_POWER_STATUS,
   GET_POWER_TIMING,
@@ -116,9 +117,6 @@ import {
 import baseMixin from './mixins/base'
 import TaskDialog, { Freq } from './mixins/TaskDialog'
 
-const LINK_MULTI = 'LinkMultiFunctionCard' // 开始上报
-const UNLINK_MULTI = 'ReleaseMultiFunctionCard' // 停止上报
-
 const ErrorMessage = {
   TIMEOUT: '暂未获取到操作反馈,请回读查看',
   TIMEOUT_RETRY: '暂未获取到操作反馈,请稍后重试',
@@ -159,8 +157,7 @@ export default {
           { label: '一键开启', on: this.onSwitchOpen },
           { label: '一键关闭', on: this.onSwitchClose },
           { label: '回读', on: this.getPowerStatus },
-          { label: '开始上报', render: () => this.isSuperAdmin, on: this.onLink },
-          { label: '停止上报', render: () => this.isSuperAdmin, on: this.onUnlink }
+          { label: '停止上报', render: () => this.isSuperAdmin, on: this.onRelease }
         ],
         cols: [
           { label: '设备', render: ({ portIndex }) => portIndex === RELAY_KEY ? '播控盒' : `控电设备${portIndex}` },
@@ -981,11 +978,8 @@ export default {
       this.$tasks.splice(index, 1)
       this.$refs.table.pageTo()
     },
-    onLink () {
-      this.sendTopic(LINK_MULTI)
-    },
-    onUnlink () {
-      this.sendTopic(UNLINK_MULTI)
+    onRelease () {
+      this.sendTopic(RELEASE_MULTI)
     }
   }
 }

+ 4 - 46
src/views/device/detail/monitor.js

@@ -1,15 +1,8 @@
-import { ThirdPartyDevice } from '@/constant'
 import {
   publish,
   subscribe,
   unsubscribe
 } from '@/utils/mqtt'
-import {
-  GET_POWER_STATUS,
-  GET_RECEIVER_INFO,
-  getPowerStatusByMessage,
-  getReceivingCardStatusByMessage
-} from '@/utils/adapter/nova'
 import { parseByte } from '@/utils'
 
 let productId = null
@@ -49,20 +42,6 @@ export function start (device) {
   } })
   createType('screen', { type: Type.CACHE, parser, reset: true })
   createType('multifunction', { parser })
-  createType('power', { type: Type.CACHE, parser: powerParse, reset: true, check: true, default () {
-    return {
-      value: {
-        [ThirdPartyDevice.MULTI_FUNCTION_CARD]: {
-          status: -1,
-          types: []
-        },
-        [ThirdPartyDevice.RECEIVING_CARD]: {
-          status: -1,
-          receivers: []
-        }
-      }
-    }
-  } })
 }
 
 export function stop () {
@@ -111,7 +90,7 @@ function getTypeBySend (topic) {
     case '/screen/ask':
       return ['screen']
     case '/multifunctionCard/invoke/reply':
-      return ['multifunction', 'power']
+      return ['multifunction']
     default:
       return []
   }
@@ -159,7 +138,7 @@ function getTypeByTopic (topic) {
     case 'screen':
       return ['screen']
     case 'multifunctionCard/invoke/reply':
-      return ['multifunction', 'power']
+      return ['multifunction']
     default:
       return []
   }
@@ -171,7 +150,7 @@ function onMessage (topic, message) {
     if (productId === result[1] && deviceId === result[2]) {
       const alias = result[3]
       getTypeByTopic(alias).forEach(key => {
-        onUpdate(types.get(key), message, alias)
+        onUpdate(types.get(key), message, alias, deviceId)
       })
     }
   }
@@ -180,7 +159,7 @@ function onMessage (topic, message) {
 function onUpdate (inst, message, topic) {
   if (inst) {
     try {
-      const value = inst.parser ? inst.parser(inst, message, topic) : message
+      const value = inst.parser ? inst.parser(inst, message, topic, deviceId) : message
       if (inst.check && value === false) {
         return
       }
@@ -302,24 +281,3 @@ function downloadParser (inst, message) {
   }
   return inst.value
 }
-
-function powerParse (inst, message) {
-  message = message && JSON.parse(message)
-
-  if (message.function === GET_POWER_STATUS) {
-    const data = getPowerStatusByMessage(message)
-    if (data.success) {
-      inst.value[ThirdPartyDevice.MULTI_FUNCTION_CARD] = data.data
-      return inst.value
-    }
-  } else if (message.function === GET_RECEIVER_INFO) {
-    const data = getReceivingCardStatusByMessage(message)
-    if (data.success) {
-      inst.value[ThirdPartyDevice.RECEIVING_CARD] = data.data
-      return inst.value
-    }
-    return inst.data
-  }
-
-  return false
-}