更新串口插件

This commit is contained in:
2026-06-03 15:49:48 +08:00
parent 923b4a3292
commit f5e47f37b3
16 changed files with 1106 additions and 1871 deletions
+131 -160
View File
@@ -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() // 内部已包含清队列 + 释放锁
},
// 解析门卡数据,返回卡号