|
|
@@ -0,0 +1,331 @@
|
|
|
+import { ThirdPartyDevice } from '@/constant'
|
|
|
+import {
|
|
|
+ WillTopic,
|
|
|
+ createClient,
|
|
|
+ decodePayload
|
|
|
+} from '@/utils/mqtt'
|
|
|
+import { getStatusReport } from '@/api/device'
|
|
|
+import { getThirdPartyDevicesByThirdPartyDevice } from '@/api/mesh'
|
|
|
+import {
|
|
|
+ Status,
|
|
|
+ Power
|
|
|
+} from './constant'
|
|
|
+import {
|
|
|
+ GET_POWER_STATUS,
|
|
|
+ GET_RECEIVER_TOPOLOGY,
|
|
|
+ GET_RECEIVER_INFO,
|
|
|
+ parseCachePower,
|
|
|
+ getPowerStatusByMessage,
|
|
|
+ getReceivingCardTopologyByMessage,
|
|
|
+ getReceivingCardStatusByMessage
|
|
|
+} from './nova'
|
|
|
+
|
|
|
+const TIMEOUT_MILLISECOND = 120000
|
|
|
+
|
|
|
+const deviceCache = new Map()
|
|
|
+const cbMap = new Map()
|
|
|
+const injectMap = new Map()
|
|
|
+
|
|
|
+let client = null
|
|
|
+export function startMonitor () {
|
|
|
+ if (client) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ client = createClient({
|
|
|
+ will: {
|
|
|
+ topic: WillTopic,
|
|
|
+ payload: 'Thirdparty device monitor has died.',
|
|
|
+ qos: 0,
|
|
|
+ retain: false
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ client.on('error', err => {
|
|
|
+ console.log('Monitor connection error: ', err)
|
|
|
+ })
|
|
|
+
|
|
|
+ client.on('connect', () => {
|
|
|
+ console.log('Monitor connected')
|
|
|
+ })
|
|
|
+
|
|
|
+ client.on('disconnect', () => {
|
|
|
+ console.log('Monitor disconnect')
|
|
|
+ })
|
|
|
+
|
|
|
+ client.on('reconnect', () => {
|
|
|
+ console.log('Monitor reconnecting...')
|
|
|
+ })
|
|
|
+
|
|
|
+ client.on('offline', () => {
|
|
|
+ console.log('Monitor offline')
|
|
|
+ })
|
|
|
+
|
|
|
+ client.on('close', () => {
|
|
|
+ console.log('Monitor closed')
|
|
|
+ })
|
|
|
+
|
|
|
+ client.on('message', (topic, payload) => {
|
|
|
+ console.log('Monitor topic', topic)
|
|
|
+ const result = /^(\d+)\/(\d+)\/(screen|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 timestamp = Number(message.timestamp) || Date.now()
|
|
|
+
|
|
|
+ if (result[3] === 'screen') {
|
|
|
+ const { versionName, versionCode, externalUsage, ramUsage, volume } = message
|
|
|
+ emit(id, 'screen', {
|
|
|
+ timestamp,
|
|
|
+ versionName,
|
|
|
+ versionCode,
|
|
|
+ externalUsage,
|
|
|
+ ramUsage,
|
|
|
+ volume
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (message.function === GET_POWER_STATUS) {
|
|
|
+ const data = getPowerStatusByMessage(message)
|
|
|
+ if (data.success) {
|
|
|
+ emit(id, ThirdPartyDevice.MULTI_FUNCTION_CARD, {
|
|
|
+ timestamp,
|
|
|
+ ...data.data
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else if (message.function === GET_RECEIVER_INFO) {
|
|
|
+ const data = getReceivingCardStatusByMessage(message)
|
|
|
+ if (data.success) {
|
|
|
+ emit(id, ThirdPartyDevice.RECEIVING_CARD, {
|
|
|
+ timestamp,
|
|
|
+ ...data.data
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else if (message.function === GET_RECEIVER_TOPOLOGY) {
|
|
|
+ const data = getReceivingCardTopologyByMessage(message)
|
|
|
+ if (data.success) {
|
|
|
+ emit(id, 'topology', {
|
|
|
+ timestamp,
|
|
|
+ ...data.data
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ injectMap.get(id)?.forEach(cb => {
|
|
|
+ cb(message)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ client.subscribe([
|
|
|
+ '+/+/screen',
|
|
|
+ '+/+/multifunctionCard/invoke/reply'
|
|
|
+ ])
|
|
|
+}
|
|
|
+
|
|
|
+function emit (id, key, value) {
|
|
|
+ const cache = getCacheById(id)
|
|
|
+ cache[key] = value
|
|
|
+ cbMap.get(id)?.forEach(cb => {
|
|
|
+ cb(cache, key)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+export function getCacheById (id) {
|
|
|
+ let cache = deviceCache.get(id)
|
|
|
+ if (!cache) {
|
|
|
+ deviceCache.set(id, cache = {
|
|
|
+ [ThirdPartyDevice.MULTI_FUNCTION_CARD]: {
|
|
|
+ status: Status.LOADING,
|
|
|
+ switchStatus: Power.LOADING,
|
|
|
+ powers: []
|
|
|
+ },
|
|
|
+ [ThirdPartyDevice.RECEIVING_CARD]: {
|
|
|
+ status: Status.LOADING,
|
|
|
+ receivers: []
|
|
|
+ },
|
|
|
+ topology: null,
|
|
|
+ screen: null
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return cache
|
|
|
+}
|
|
|
+
|
|
|
+export function addListener (id, cb) {
|
|
|
+ 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 add', id, '->', 'success')
|
|
|
+ cbSet.add(cb)
|
|
|
+ const cache = getCacheById(id)
|
|
|
+ checkMultiCard(id, cache[ThirdPartyDevice.MULTI_FUNCTION_CARD])
|
|
|
+ cb(cache)
|
|
|
+}
|
|
|
+
|
|
|
+function checkMultiCard (id, multiCard) {
|
|
|
+ switch (multiCard.status) {
|
|
|
+ case Status.LOADING:
|
|
|
+ console.log('monitor', id, '->', 'add task')
|
|
|
+ addTask(id)
|
|
|
+ break
|
|
|
+ case Status.OK:
|
|
|
+ if (Date.now() - multiCard.timestamp > TIMEOUT_MILLISECOND) {
|
|
|
+ multiCard.status = Status.WARNING
|
|
|
+ }
|
|
|
+ break
|
|
|
+ default:
|
|
|
+ break
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export function removeListener (id, cb) {
|
|
|
+ const cbSet = cbMap.get(id)
|
|
|
+ if (!cbSet || !cbSet.has(cb)) {
|
|
|
+ console.log('monitor', id, '->', 'not exsit')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ console.log('monitor remove', id, '->', 'success')
|
|
|
+ cbSet.delete(cb)
|
|
|
+}
|
|
|
+
|
|
|
+export function addInjectListener (id, cb) {
|
|
|
+ let cbSet = injectMap.get(id)
|
|
|
+ if (!cbSet) {
|
|
|
+ injectMap.set(id, cbSet = new Set())
|
|
|
+ }
|
|
|
+ if (cbSet.has(cb)) {
|
|
|
+ console.log('inject', id, '->', 'exsit')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ console.log('inject add', id, '->', 'success')
|
|
|
+ cbSet.add(cb)
|
|
|
+}
|
|
|
+
|
|
|
+export function removeInjectListener (id, cb) {
|
|
|
+ const cbSet = injectMap.get(id)
|
|
|
+ if (!cbSet || !cbSet.has(cb)) {
|
|
|
+ console.log('inject', id, '->', 'not exsit')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ console.log('inject remove', id, '->', 'success')
|
|
|
+ cbSet.delete(cb)
|
|
|
+}
|
|
|
+
|
|
|
+let waiting = new Set()
|
|
|
+let queue = new Set()
|
|
|
+let fetching = false
|
|
|
+let started = false
|
|
|
+
|
|
|
+function addTask (id) {
|
|
|
+ if (fetching) {
|
|
|
+ if (queue.has(id)) {
|
|
|
+ console.log('task exsit', id)
|
|
|
+ } else {
|
|
|
+ waiting.add(id)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ queue.add(id)
|
|
|
+ flush()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function flush () {
|
|
|
+ if (!started && (queue.size || waiting.size)) {
|
|
|
+ queue = new Set([...queue, ...waiting])
|
|
|
+ waiting = new Set()
|
|
|
+ started = true
|
|
|
+ Promise.resolve().then(startTask)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function startTask () {
|
|
|
+ fetching = true
|
|
|
+ console.log('monitor task', queue.size)
|
|
|
+ const ids = [...queue]
|
|
|
+ getStatusReport(ids, { background: true, custom: true }).then(
|
|
|
+ async ({ data }) => {
|
|
|
+ const map = {}
|
|
|
+ data.forEach(item => {
|
|
|
+ map[item.deviceId] = item
|
|
|
+ })
|
|
|
+ const now = Date.now()
|
|
|
+ const errorSet = new Set()
|
|
|
+ for (let i = 0; i < ids.length; i++) {
|
|
|
+ const id = ids[i]
|
|
|
+ if (getCacheById(id)[ThirdPartyDevice.MULTI_FUNCTION_CARD].status !== Status.LOADING) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ const multiCard = map[id]
|
|
|
+ // 未上报过数据需判断是否绑定了多功能卡
|
|
|
+ if (!multiCard) {
|
|
|
+ try {
|
|
|
+ const { data } = await getThirdPartyDevicesByThirdPartyDevice(id, [
|
|
|
+ ThirdPartyDevice.MULTI_FUNCTION_CARD
|
|
|
+ ], { background: true, custom: true })
|
|
|
+ if (data?.[0]?.instance) {
|
|
|
+ emit(id, ThirdPartyDevice.MULTI_FUNCTION_CARD, {
|
|
|
+ status: Status.WARNING,
|
|
|
+ timestamp: Date.now(),
|
|
|
+ switchStatus: Power.LOADING,
|
|
|
+ powers: []
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ emit(id, ThirdPartyDevice.MULTI_FUNCTION_CARD, { status: Status.NONE })
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ errorSet.add(id)
|
|
|
+ }
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ const timestamp = Number(multiCard.reportTimestamp)
|
|
|
+ const powerInfo = parseCachePower(multiCard)
|
|
|
+ // 超时未上报的需判断是否解绑了多功能卡
|
|
|
+ if (now - timestamp > TIMEOUT_MILLISECOND) {
|
|
|
+ try {
|
|
|
+ const { data } = await getThirdPartyDevicesByThirdPartyDevice(id, [
|
|
|
+ ThirdPartyDevice.MULTI_FUNCTION_CARD
|
|
|
+ ], { background: true, custom: true })
|
|
|
+ if (data?.[0]?.instance) {
|
|
|
+ emit(id, ThirdPartyDevice.MULTI_FUNCTION_CARD, {
|
|
|
+ status: Status.WARNING,
|
|
|
+ timestamp,
|
|
|
+ ...powerInfo
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ emit(id, ThirdPartyDevice.MULTI_FUNCTION_CARD, { status: Status.NONE })
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ errorSet.add(id)
|
|
|
+ }
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if (multiCard.switchStatus === Power.LOADING) {
|
|
|
+ emit(id, ThirdPartyDevice.MULTI_FUNCTION_CARD, {
|
|
|
+ status: Status.WARNING,
|
|
|
+ timestamp,
|
|
|
+ ...powerInfo
|
|
|
+ })
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ emit(id, ThirdPartyDevice.MULTI_FUNCTION_CARD, {
|
|
|
+ status: Status.OK,
|
|
|
+ timestamp,
|
|
|
+ ...powerInfo
|
|
|
+ })
|
|
|
+ }
|
|
|
+ queue = errorSet
|
|
|
+ }
|
|
|
+ ).then(() => {
|
|
|
+ fetching = false
|
|
|
+ started = false
|
|
|
+ setTimeout(flush, 500)
|
|
|
+ })
|
|
|
+}
|