Files
endo_an_2/utils/cmd.js
T
2026-06-02 09:15:38 +08:00

391 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 接收数据
// 压差站号:01,温湿度站号:02,门控制站号:03
// 压差 01 03 02 12 34 B5 33
// 温湿度 01 03 04 02 98 00 BC 7B D5
// IC卡 20 01 00 08 04 00 00 00 0e 26 fe ab 8f 03
// ID卡 20 01 00 05 49 00 b5 6a b5 d8 03
// 卡离开 20 01 00 01 02 fd 03
// 门控制指令(中盛4路数字IO模块,功能码06):
// 左门开门: 03 06 00 00 00 01 49 E8
// 左门关门: 03 06 00 00 00 00 88 28
// 右门开门: 03 06 00 01 00 01 18 28
// 右门关门: 03 06 00 01 00 00 D9 E8
// 卡片放入和离开 起始位20, 所有卡片通过同一通道读取,通过数据库比对判断类型
// let RS232 = getApp().globalData.RS232
import store from '@/store/index.js'
// ========== RS485 优先级消息队列 START ==========
const PRIORITY = {
HIGH: 0, // 用户操作(开门/关门/控制指令)
LOW: 1, // 定时轮询(getTemp/getPressure
}
const rs485Queue = []
let isRS485Sending = false
const RS485_SEND_DELAY = 200 // 每条指令间隔200ms,确保总线空闲
const MAX_QUEUE_SIZE = 3 // 队列最大长度
/**
* 将任务按优先级插入队列
* 规则:
* 1. 队列最大长度为3
* 2. 重复指令(相同cmd)不入队,直接丢弃
* 3. 队列满时,高优先级任务挤掉低优先级任务,否则踢掉最老的(队首)
*/
const enqueueTask = (task) => {
// 规则1: 去重 - 已存在相同cmd的任务则丢弃
const isDuplicate = rs485Queue.some(t => t.cmd === task.cmd)
if (isDuplicate) {
// console.warn('[RS485队列] 丢弃重复任务:', task.cmd)
return false
}
// 规则2: 队列满了,先腾位置
if (rs485Queue.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) {
lastLowIndex = i
break
}
}
if (lastLowIndex !== -1) {
// console.warn('[RS485队列] 高优挤掉低优:', rs485Queue[lastLowIndex].cmd)
rs485Queue.splice(lastLowIndex, 1)
} else {
// 没有低优先级可挤,踢掉队首最老的
// console.warn('[RS485队列] 满,踢掉队首:', rs485Queue[0].cmd)
rs485Queue.shift()
}
} else {
// 低优先级:直接踢掉队首最老的
// console.warn('[RS485队列] 满,踢掉队首:', rs485Queue[0].cmd)
rs485Queue.shift()
}
}
// 规则3: 按优先级插入
if (task.priority === PRIORITY.HIGH) {
let insertIndex = rs485Queue.findIndex(t => t.priority === PRIORITY.LOW)
if (insertIndex === -1) {
rs485Queue.push(task)
} else {
rs485Queue.splice(insertIndex, 0, task)
}
} else {
rs485Queue.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) => {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
console.warn(`[RS485] 发送超时(${RS485_SEND_TIMEOUT}ms), cmd: ${cmd}`)
reject(new Error('RS485发送超时'))
}, RS485_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 ==========
export default {
// 开左门指令 (通过RS485站号03) - 高优先级
// 中盛4路数字IO模块,功能码06(写单个保持寄存器)
LeftDoor: async (status) => {
// 左门开门: 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)
},
// 开右门指令 (通过RS485站号03) - 高优先级
// 中盛4路数字IO模块,功能码06(写单个保持寄存器)
RightDoor: async (status) => {
// 右门开门: 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)
},
// 开门关门控制 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
Wind: async (status) => {
let op = status ? '01' : '00'
let cmd = '5AA5EE02' + op
store.state.relay.wind = status
let RS232 = getApp().globalData.RS232
await RS232.sendDataString(cmd);
},
// 开灯指令 03
Light: async (status) => {
let op = status ? '01' : '00'
let cmd = '5AA5EE03' + op
store.state.relay.light = status
let RS232 = getApp().globalData.RS232
await RS232.sendDataString(cmd);
},
// 开真空指令 04
Vacuum: async (status) => {
let op = status ? '01' : '00'
let cmd = '5AA5EE04' + op
store.state.relay.vacuum = status
let RS232 = getApp().globalData.RS232
await RS232.sendDataString(cmd);
},
// 开关消毒指令 05
Disinfect: async (status) => {
let op = status ? '01' : '00'
let cmd = '5AA5EE05' + op
store.state.relay.disinfect = status
let RS232 = getApp().globalData.RS232
await RS232.sendDataString(cmd);
},
// 获取温湿度(通过RS485站号02)- 低优先级
getTemp: () => {
let cmd = '020300000002C438';
return enqueueRS485Send(cmd, PRIORITY.LOW)
},
// 获取压差指令(通过RS485站号01)- 低优先级
getPressure: () => {
let cmd = '01030001000295CB'
return enqueueRS485Send(cmd, PRIORITY.LOW)
},
// 导出队列管理方法
clearRS485Queue,
getRS485QueueSize,
// 重置RS485队列锁(供外部重连时调用)
resetRS485Lock() {
clearRS485Queue() // 内部已包含清队列 + 释放锁
},
// 解析门卡数据,返回卡号
parse232dData: (hexString) => {
// IC卡 20 01 00 08 04 00 00 00 0e 26 fe ab 8f 03
// ID卡 20 01 00 05 49 00 b5 6a b5 d8 03
// 卡离开 20 01 00 01 02 fd 03
// 01 地址, 04 00 卡类型,0e 26 fe ab 卡号,8f 校验 03固定值
// 人员卡地址号0,卡号4个字节,倒数4个字节,倒数一位03固定值,倒数二位校验
// 内镜卡地址号1-16,卡号4个字节,倒数4个字节,倒数一位03固定值,倒数二位校验
// 卡离开8位,卡进入11位和14位
// 移除空格和换行符
const hexStr = hexString.replace(/\s+/g, '');
// 验证输入
if (hexStr.length === 0) {
throw new Error('请输入Modbus十六进制数据');
}
if (hexStr.length % 2 !== 0) {
throw new Error('请输入有效的Modbus十六进制数据');
}
// 解析后的数据对象
let data = {};
// 刷卡数据 起始字节20,第二字节站号,结束字节03
if (hexStr.substr(0, 2) == '20' && hexStr.substr(hexStr.length - 2, 2) == '03') {
// 卡数据
let hexNum = hexStr.substr(hexStr.length - 12, 8)
// 第二位是地址
let addHex = hexStr.substr(2, 2)
// 转换成10进制地址号
let address = parseInt(addHex, 16)
// 解析后的数据 { address: 0, number: 12345678, cardType: 'ic', action: 'enter', type: 'person' }
data.address = address
if (hexStr.length == 28) {
// 14字节是IC卡号
let icNo = parseInt(hexNum, 16)
data.number = icNo
data.cardType = 'ic'
data.action = 'enter'
}
if (hexStr.length == 22) {
// 11字节是ID卡号
let idNo = parseInt(hexNum, 16)
data.number = idNo
data.cardType = 'id'
data.action = 'enter'
}
if (hexStr.length == 14) {
// 7字节是卡离开
data.action = 'leave'
}
if (address == 0) {
// 人员卡
data.type = 'person'
}
if (address >= 1 && address <= 16) {
// 内镜卡
data.type = 'internal'
}
}
// 关门上报指令
// if (hexStr.toUpperCase() == '5AA50000000000FF') {
// // 开门数据 8个字节
// data.action = 'door'
// }
return data
},
parse485Data: (hexString) => {
// 移除空格和换行符
const hexStr = hexString.replace(/\s+/g, '');
// 压差站号:01,温湿度站号:02
let addHex = hexStr.substr(0, 2)
let data = {}
if (hexStr.length == 14 || hexStr.length == 18) {
if (addHex == '01') {
let pressure = parseInt(hexStr.substr(6, 4), 16);
data.pressure = pressure / 10
}
if (addHex == '02') {
let humi = parseInt(hexStr.substr(6, 4), 16);
let temp = parseInt(hexStr.substr(10, 4), 16);
// 温湿度上报数据异常处理,取正常温湿度范围内数据
temp = (temp / 10).toFixed(1)
if (temp >= 0 && temp < 100) {
data.temp = temp
}
humi = (humi / 10).toFixed(1)
if (humi >= 0 && humi < 100) {
data.humi = humi
}
}
if (addHex == '03') {
// 中盛4路数字IO控制器, 站号:03
// 左门开门: 03 06 00 01 00 01 18 28
// 左门关门: 03 06 00 01 00 00 D9 E8
// 03040200010130
// 右门开门: 03 06 00 00 00 01 49 E8
// 右门关门: 03 06 00 00 00 00 88 28
// 03040200024131
// 关门状态上报(功能码04,读输入寄存器):
// DI状态变化上报,解析左右门触点触发
let funcHex = hexStr.substr(8, 2);
if (funcHex == '01') {
// 左门关
data.action = 'LeftDoor'
data.status = 'closed'
}
if (funcHex == '02') {
// 右门关
data.action = 'RightDoor'
data.status = 'closed'
}
if (funcHex == '03') {
// 2个门关
data.action = 'door'
data.status = 'closed'
}
// console.log('parse485Data:',hexStr, data)
}
}
return data
},
}