更新串口插件
This commit is contained in:
+131
-160
@@ -16,179 +16,84 @@
|
||||
// let RS232 = getApp().globalData.RS232
|
||||
import store from '@/store/index.js'
|
||||
|
||||
// ========== RS485 优先级消息队列 START ==========
|
||||
// ========== 统一消息队列 START ==========
|
||||
// RS485 和 RS232 共用同一队列,通过 type 字段区分串口实例
|
||||
// 互斥锁确保同一时刻只有一个串口在发送指令,杜绝双串口并发竞态
|
||||
const PRIORITY = {
|
||||
HIGH: 0, // 用户操作(开门/关门/控制指令)
|
||||
LOW: 1, // 定时轮询(getTemp/getPressure)
|
||||
LOW: 1, // 定时轮询(getTemp/getPressure)
|
||||
}
|
||||
|
||||
const rs485Queue = []
|
||||
let isRS485Sending = false
|
||||
const RS485_SEND_DELAY = 200 // 每条指令间隔200ms,确保总线空闲
|
||||
const MAX_QUEUE_SIZE = 3 // 队列最大长度
|
||||
const queue = []
|
||||
let isSending = false
|
||||
const SEND_DELAY = 150 // 发送间隔150ms
|
||||
const MAX_QUEUE_SIZE = 5 // 队列最大长度
|
||||
const SEND_TIMEOUT = 1000 // 统一超时1秒
|
||||
|
||||
/**
|
||||
* 将任务按优先级插入队列
|
||||
* 规则:
|
||||
* 1. 队列最大长度为3
|
||||
* 2. 重复指令(相同cmd)不入队,直接丢弃
|
||||
* 1. 队列最大长度为5
|
||||
* 2. 重复指令(相同type+cmd)不入队,直接丢弃
|
||||
* 3. 队列满时,高优先级任务挤掉低优先级任务,否则踢掉最老的(队首)
|
||||
*/
|
||||
const enqueueTask = (task) => {
|
||||
// 规则1: 去重 - 已存在相同cmd的任务则丢弃
|
||||
const isDuplicate = rs485Queue.some(t => t.cmd === task.cmd)
|
||||
// 规则1: 去重 - 已存在相同type+cmd的任务则丢弃
|
||||
const isDuplicate = queue.some(t => t.cmd === task.cmd && t.type === task.type)
|
||||
if (isDuplicate) {
|
||||
// console.warn('[RS485队列] 丢弃重复任务:', task.cmd)
|
||||
return false
|
||||
}
|
||||
|
||||
// 规则2: 队列满了,先腾位置
|
||||
if (rs485Queue.length >= MAX_QUEUE_SIZE) {
|
||||
if (queue.length >= MAX_QUEUE_SIZE) {
|
||||
if (task.priority === PRIORITY.HIGH) {
|
||||
// 高优先级:优先挤掉队列中最后一个低优先级任务
|
||||
let lastLowIndex = -1
|
||||
for (let i = rs485Queue.length - 1; i >= 0; i--) {
|
||||
if (rs485Queue[i].priority === PRIORITY.LOW) {
|
||||
for (let i = queue.length - 1; i >= 0; i--) {
|
||||
if (queue[i].priority === PRIORITY.LOW) {
|
||||
lastLowIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (lastLowIndex !== -1) {
|
||||
// console.warn('[RS485队列] 高优挤掉低优:', rs485Queue[lastLowIndex].cmd)
|
||||
rs485Queue.splice(lastLowIndex, 1)
|
||||
queue.splice(lastLowIndex, 1)
|
||||
} else {
|
||||
// 没有低优先级可挤,踢掉队首最老的
|
||||
// console.warn('[RS485队列] 满,踢掉队首:', rs485Queue[0].cmd)
|
||||
rs485Queue.shift()
|
||||
queue.shift()
|
||||
}
|
||||
} else {
|
||||
// 低优先级:直接踢掉队首最老的
|
||||
// console.warn('[RS485队列] 满,踢掉队首:', rs485Queue[0].cmd)
|
||||
rs485Queue.shift()
|
||||
queue.shift()
|
||||
}
|
||||
}
|
||||
|
||||
// 规则3: 按优先级插入
|
||||
if (task.priority === PRIORITY.HIGH) {
|
||||
let insertIndex = rs485Queue.findIndex(t => t.priority === PRIORITY.LOW)
|
||||
let insertIndex = queue.findIndex(t => t.priority === PRIORITY.LOW)
|
||||
if (insertIndex === -1) {
|
||||
rs485Queue.push(task)
|
||||
queue.push(task)
|
||||
} else {
|
||||
rs485Queue.splice(insertIndex, 0, task)
|
||||
queue.splice(insertIndex, 0, task)
|
||||
}
|
||||
} else {
|
||||
rs485Queue.push(task)
|
||||
queue.push(task)
|
||||
}
|
||||
|
||||
// console.log(`[RS485队列] 入队成功, 长度:${rs485Queue.length}, cmd:${task.cmd}`)
|
||||
return true
|
||||
}
|
||||
|
||||
// ========== RS485 发送超时保护 ==========
|
||||
const RS485_SEND_TIMEOUT = 2000 // 发送超时2秒
|
||||
|
||||
/**
|
||||
* 带超时的RS485发送封装
|
||||
* 带超时的统一发送封装
|
||||
* 防止底层串口驱动卡死导致Promise永久pending,造成队列死锁
|
||||
*/
|
||||
const sendWithTimeout = (rs485Instance, cmd) => {
|
||||
const sendWithTimeout = (instance, cmd) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
console.warn(`[RS485] 发送超时(${RS485_SEND_TIMEOUT}ms), cmd: ${cmd}`)
|
||||
reject(new Error('RS485发送超时'))
|
||||
}, RS485_SEND_TIMEOUT)
|
||||
console.warn(`[统一队列] 发送超时(${SEND_TIMEOUT}ms), cmd: ${cmd}`)
|
||||
reject(new Error('发送超时'))
|
||||
}, SEND_TIMEOUT)
|
||||
|
||||
const result = rs485Instance.sendDataString(cmd)
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(res => {
|
||||
clearTimeout(timer)
|
||||
resolve(res)
|
||||
}).catch(err => {
|
||||
clearTimeout(timer)
|
||||
reject(err)
|
||||
})
|
||||
} else {
|
||||
clearTimeout(timer)
|
||||
resolve(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
// ========== RS485 发送超时保护 END ==========
|
||||
|
||||
const processRS485Queue = async () => {
|
||||
if (isRS485Sending || rs485Queue.length === 0) {
|
||||
return
|
||||
}
|
||||
isRS485Sending = true
|
||||
const task = rs485Queue.shift()
|
||||
try {
|
||||
let RS485 = getApp().globalData.RS485
|
||||
if (!RS485) {
|
||||
throw new Error('RS485 not initialized')
|
||||
}
|
||||
// 使用带超时的发送封装,防止Promise永久pending导致队列死锁
|
||||
await sendWithTimeout(RS485, task.cmd)
|
||||
// 发送后延迟,确保总线空闲再发下一条
|
||||
await new Promise(r => setTimeout(r, RS485_SEND_DELAY))
|
||||
task.resolve()
|
||||
} catch (error) {
|
||||
console.error('RS485 send error:', error)
|
||||
task.reject(error)
|
||||
} finally {
|
||||
isRS485Sending = false
|
||||
processRS485Queue()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送RS485指令(带优先级)
|
||||
* @param {string} cmd - 十六进制指令字符串
|
||||
* @param {number} priority - 优先级,默认HIGH
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const enqueueRS485Send = (cmd, priority = PRIORITY.HIGH) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
enqueueTask({ cmd, priority, resolve, reject })
|
||||
processRS485Queue()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空队列(重连时调用,拒绝所有待发送请求)
|
||||
*/
|
||||
const clearRS485Queue = () => {
|
||||
while (rs485Queue.length > 0) {
|
||||
const task = rs485Queue.shift()
|
||||
task.reject(new Error('RS485队列已清空(重连中)'))
|
||||
}
|
||||
isRS485Sending = false // 强制释放发送锁,防止队列死锁
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前队列长度(调试用)
|
||||
*/
|
||||
const getRS485QueueSize = () => {
|
||||
return rs485Queue.length
|
||||
}
|
||||
// ========== RS485 优先级消息队列 END ==========
|
||||
|
||||
// ========== RS232 超时保护 START ==========
|
||||
// RS232波特率115200极快(~1ms/条),无需队列,仅需超时保护防止Promise永久pending
|
||||
|
||||
const RS232_SEND_TIMEOUT = 1000 // 发送超时1秒(对115200绰绰有余)
|
||||
|
||||
/**
|
||||
* 带超时的RS232发送封装
|
||||
* 防止底层串口驱动卡死导致Promise永久pending
|
||||
*/
|
||||
const sendRS232WithTimeout = (rs232Instance, cmd) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
console.warn(`[RS232] 发送超时(${RS232_SEND_TIMEOUT}ms), cmd: ${cmd}`)
|
||||
reject(new Error('RS232发送超时'))
|
||||
}, RS232_SEND_TIMEOUT)
|
||||
|
||||
const result = rs232Instance.sendDataString(cmd)
|
||||
const result = instance.sendDataString(cmd)
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(res => { clearTimeout(timer); resolve(res) })
|
||||
.catch(err => { clearTimeout(timer); reject(err) })
|
||||
@@ -198,7 +103,73 @@ const sendRS232WithTimeout = (rs232Instance, cmd) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
// ========== RS232 超时保护 END ==========
|
||||
|
||||
/**
|
||||
* 处理队列中的下一个任务
|
||||
* 根据 task.type 自动获取对应串口实例(RS485 或 RS232)
|
||||
*/
|
||||
const processQueue = async () => {
|
||||
if (isSending || queue.length === 0) {
|
||||
return
|
||||
}
|
||||
isSending = true
|
||||
const task = queue.shift()
|
||||
|
||||
try {
|
||||
// 根据 type 获取对应的串口实例
|
||||
let instance = getApp().globalData[task.type]
|
||||
if (!instance) {
|
||||
throw new Error(`${task.type} not initialized`)
|
||||
}
|
||||
|
||||
// 统一发送(带超时保护)
|
||||
await sendWithTimeout(instance, task.cmd)
|
||||
|
||||
// 发送后延迟,确保总线空闲再发下一条
|
||||
await new Promise(r => setTimeout(r, SEND_DELAY))
|
||||
|
||||
task.resolve()
|
||||
} catch (error) {
|
||||
console.error('[统一队列] send error:', error)
|
||||
task.reject(error)
|
||||
} finally {
|
||||
isSending = false
|
||||
processQueue() // 处理下一条
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 入队接口
|
||||
* @param {string} type - 'RS485' 或 'RS232'
|
||||
* @param {string} cmd - 十六进制指令字符串
|
||||
* @param {number} priority - 优先级,默认HIGH
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const enqueue = (type, cmd, priority = PRIORITY.HIGH) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
enqueueTask({ type, cmd, priority, resolve, reject })
|
||||
processQueue()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空队列(重连时调用,拒绝所有待发送请求)
|
||||
*/
|
||||
const clearQueue = () => {
|
||||
while (queue.length > 0) {
|
||||
const task = queue.shift()
|
||||
task.reject(new Error('队列已清空(重连中)'))
|
||||
}
|
||||
isSending = false // 强制释放发送锁,防止队列死锁
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前队列长度(调试用)
|
||||
*/
|
||||
const getQueueSize = () => {
|
||||
return queue.length
|
||||
}
|
||||
// ========== 统一消息队列 END ==========
|
||||
|
||||
export default {
|
||||
// 开左门指令 (通过RS485站号03) - 高优先级
|
||||
@@ -207,10 +178,16 @@ export default {
|
||||
// 左门开门: 03 06 00 00 00 01 49 E8
|
||||
// 左门关门: 03 06 00 00 00 00 88 28
|
||||
let cmd = status ? '03060000000149E8' : '0306000000008828'
|
||||
store.state.relay.leftDoor = status
|
||||
// door状态 = leftDoor OR rightDoor (任一门打开则door为true)
|
||||
store.state.relay.door = store.state.relay.leftDoor || store.state.relay.rightDoor
|
||||
await enqueueRS485Send(cmd, PRIORITY.HIGH)
|
||||
try {
|
||||
await enqueue('RS485', cmd, PRIORITY.HIGH)
|
||||
// 状态更新移到await之后,防止Vue Watcher提前触发RS232并发
|
||||
store.state.relay.leftDoor = status
|
||||
// door状态 = leftDoor OR rightDoor (任一门打开则door为true)
|
||||
store.state.relay.door = store.state.relay.leftDoor || store.state.relay.rightDoor
|
||||
} catch (error) {
|
||||
console.error('[LeftDoor] error:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 开右门指令 (通过RS485站号03) - 高优先级
|
||||
@@ -219,71 +196,65 @@ export default {
|
||||
// 右门开门: 03 06 00 01 00 01 18 28
|
||||
// 右门关门: 03 06 00 01 00 00 D9 E8
|
||||
let cmd = status ? '0306000100011828' : '030600010000D9E8'
|
||||
store.state.relay.rightDoor = status
|
||||
// door状态 = leftDoor OR rightDoor (任一门打开则door为true)
|
||||
store.state.relay.door = store.state.relay.leftDoor || store.state.relay.rightDoor
|
||||
await enqueueRS485Send(cmd, PRIORITY.HIGH)
|
||||
try {
|
||||
await enqueue('RS485', cmd, PRIORITY.HIGH)
|
||||
// 状态更新移到await之后,防止Vue Watcher提前触发RS232并发
|
||||
store.state.relay.rightDoor = status
|
||||
// door状态 = leftDoor OR rightDoor (任一门打开则door为true)
|
||||
store.state.relay.door = store.state.relay.leftDoor || store.state.relay.rightDoor
|
||||
} catch (error) {
|
||||
console.error('[RightDoor] error:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
// 开门关门控制 01
|
||||
// Door: async (status) => {
|
||||
// let op = status ? '01' : '00'
|
||||
// let cmd = '5AA5EE01' + op
|
||||
// store.state.relay.door = status
|
||||
// let RS232 = getApp().globalData.RS232
|
||||
// await RS232.sendDataString(cmd);
|
||||
// },
|
||||
// 开风机指令 02
|
||||
// 开风机指令 02 (通过RS232) - 高优先级
|
||||
Wind: async (status) => {
|
||||
let op = status ? '01' : '00'
|
||||
let cmd = '5AA5EE02' + op
|
||||
store.state.relay.wind = status
|
||||
let RS232 = getApp().globalData.RS232
|
||||
await sendRS232WithTimeout(RS232, cmd)
|
||||
return enqueue('RS232', cmd, PRIORITY.HIGH)
|
||||
},
|
||||
// 开灯指令 03
|
||||
// 开灯指令 03 (通过RS232) - 高优先级
|
||||
Light: async (status) => {
|
||||
let op = status ? '01' : '00'
|
||||
let cmd = '5AA5EE03' + op
|
||||
store.state.relay.light = status
|
||||
let RS232 = getApp().globalData.RS232
|
||||
await sendRS232WithTimeout(RS232, cmd)
|
||||
return enqueue('RS232', cmd, PRIORITY.HIGH)
|
||||
},
|
||||
// 开真空指令 04
|
||||
// 开真空指令 04 (通过RS232) - 高优先级
|
||||
Vacuum: async (status) => {
|
||||
let op = status ? '01' : '00'
|
||||
let cmd = '5AA5EE04' + op
|
||||
store.state.relay.vacuum = status
|
||||
let RS232 = getApp().globalData.RS232
|
||||
await sendRS232WithTimeout(RS232, cmd)
|
||||
return enqueue('RS232', cmd, PRIORITY.HIGH)
|
||||
},
|
||||
// 开关消毒指令 05
|
||||
// 开关消毒指令 05 (通过RS232) - 高优先级
|
||||
Disinfect: async (status) => {
|
||||
let op = status ? '01' : '00'
|
||||
let cmd = '5AA5EE05' + op
|
||||
store.state.relay.disinfect = status
|
||||
let RS232 = getApp().globalData.RS232
|
||||
await sendRS232WithTimeout(RS232, cmd)
|
||||
return enqueue('RS232', cmd, PRIORITY.HIGH)
|
||||
},
|
||||
// 获取温湿度(通过RS485站号02)- 低优先级
|
||||
getTemp: () => {
|
||||
let cmd = '020300000002C438';
|
||||
return enqueueRS485Send(cmd, PRIORITY.LOW)
|
||||
return enqueue('RS485', cmd, PRIORITY.LOW)
|
||||
},
|
||||
|
||||
// 获取压差指令(通过RS485站号01)- 低优先级
|
||||
getPressure: () => {
|
||||
let cmd = '01030001000295CB'
|
||||
return enqueueRS485Send(cmd, PRIORITY.LOW)
|
||||
return enqueue('RS485', cmd, PRIORITY.LOW)
|
||||
},
|
||||
|
||||
// 导出队列管理方法
|
||||
clearRS485Queue,
|
||||
getRS485QueueSize,
|
||||
clearQueue,
|
||||
getQueueSize,
|
||||
|
||||
// 重置RS485队列锁(供外部重连时调用)
|
||||
resetRS485Lock() {
|
||||
clearRS485Queue() // 内部已包含清队列 + 释放锁
|
||||
// 重置队列锁(供外部重连时调用)
|
||||
resetLock() {
|
||||
clearQueue() // 内部已包含清队列 + 释放锁
|
||||
},
|
||||
|
||||
// 解析门卡数据,返回卡号
|
||||
|
||||
Reference in New Issue
Block a user