744 lines
24 KiB
JavaScript
744 lines
24 KiB
JavaScript
// uni-app兼容的MQTT客户端
|
||
import { MQTT_CONFIG, DataParser } from '@/config/mqtt'
|
||
|
||
// 兼容性处理:为App环境提供TextEncoder和TextDecoder
|
||
if (typeof TextEncoder === 'undefined') {
|
||
global.TextEncoder = class {
|
||
encode(str) {
|
||
const utf8 = unescape(encodeURIComponent(str));
|
||
const result = new Uint8Array(utf8.length);
|
||
for (let i = 0; i < utf8.length; i++) {
|
||
result[i] = utf8.charCodeAt(i);
|
||
}
|
||
return result;
|
||
}
|
||
};
|
||
}
|
||
|
||
if (typeof TextDecoder === 'undefined') {
|
||
global.TextDecoder = class {
|
||
constructor(encoding = 'utf-8') {
|
||
this.encoding = encoding;
|
||
}
|
||
|
||
decode(bytes) {
|
||
if (this.encoding === 'utf-8') {
|
||
let result = '';
|
||
for (let i = 0; i < bytes.length; i++) {
|
||
result += String.fromCharCode(bytes[i]);
|
||
}
|
||
try {
|
||
return decodeURIComponent(escape(result));
|
||
} catch (e) {
|
||
return result;
|
||
}
|
||
} else if (this.encoding === 'ascii') {
|
||
let result = '';
|
||
for (let i = 0; i < bytes.length; i++) {
|
||
result += String.fromCharCode(bytes[i]);
|
||
}
|
||
return result;
|
||
}
|
||
return '';
|
||
}
|
||
};
|
||
}
|
||
|
||
class UniMqttClient {
|
||
constructor() {
|
||
this.socketTask = null
|
||
this.isConnected = false
|
||
this.subscriptions = new Map()
|
||
this.messageHandlers = new Map()
|
||
this.reconnectAttempts = 0
|
||
this.maxReconnectAttempts = 5
|
||
this.reconnectTimer = null
|
||
this.heartbeatTimer = null
|
||
this.messageId = 1
|
||
}
|
||
|
||
// 连接MQTT服务器
|
||
async connect() {
|
||
try {
|
||
console.log('🔧 ===== MQTT连接开始 =====')
|
||
console.log('🔧 正在连接MQTT服务器:', MQTT_CONFIG.broker)
|
||
console.log('🔧 环境检测:', {
|
||
isApp: typeof plus !== 'undefined' || typeof window === 'undefined',
|
||
plus: typeof plus,
|
||
window: typeof window
|
||
})
|
||
|
||
// 发送日志到页面
|
||
this.sendLogToPage('===== MQTT连接开始 =====', 'info')
|
||
this.sendLogToPage(`正在连接MQTT服务器: ${MQTT_CONFIG.broker}`, 'info')
|
||
this.sendLogToPage(`环境检测: isApp=${typeof plus !== 'undefined' || typeof window === 'undefined'}, plus=${typeof plus}, window=${typeof window}`, 'info')
|
||
|
||
// 添加连接超时保护
|
||
const connectTimeout = setTimeout(() => {
|
||
console.error('🔧 MQTT连接超时')
|
||
this.sendLogToPage('MQTT连接超时', 'error')
|
||
this.handleConnectionError(new Error('Connection timeout'))
|
||
}, 10000) // 10秒超时
|
||
|
||
// 解析WebSocket URL
|
||
const wsUrl = MQTT_CONFIG.broker.replace('ws://', '').replace('wss://', '')
|
||
const [host, path] = wsUrl.split('/')
|
||
const [hostname, port] = host.split(':')
|
||
|
||
const protocol = MQTT_CONFIG.broker.startsWith('wss://') ? 'wss' : 'ws'
|
||
const fullUrl = `${protocol}://${hostname}:${port || (protocol === 'wss' ? 443 : 80)}/${path || ''}`
|
||
|
||
console.log('🔧 WebSocket连接地址:', fullUrl)
|
||
console.log('🔧 解析结果:', { hostname, port, path, protocol })
|
||
|
||
this.sendLogToPage(`WebSocket连接地址: ${fullUrl}`, 'info')
|
||
this.sendLogToPage(`解析结果: hostname=${hostname}, port=${port}, path=${path}, protocol=${protocol}`, 'info')
|
||
|
||
this.socketTask = uni.connectSocket({
|
||
url: fullUrl,
|
||
protocols: ['mqtt'],
|
||
success: () => {
|
||
console.log('🔧 WebSocket连接请求发送成功')
|
||
this.sendLogToPage('WebSocket连接请求发送成功', 'info')
|
||
},
|
||
fail: (error) => {
|
||
console.error('🔧 WebSocket连接失败:', error)
|
||
console.error('🔧 连接参数:', { url: fullUrl, protocols: ['mqtt'] })
|
||
this.sendLogToPage(`WebSocket连接失败: ${JSON.stringify(error)}`, 'error')
|
||
this.handleConnectionError(error)
|
||
}
|
||
})
|
||
|
||
return new Promise((resolve, reject) => {
|
||
this.socketTask.onOpen(() => {
|
||
console.log('🔧 WebSocket连接成功')
|
||
console.log('🔧 开始发送MQTT CONNECT消息...')
|
||
this.sendLogToPage('WebSocket连接成功', 'success')
|
||
this.sendLogToPage('开始发送MQTT CONNECT消息...', 'info')
|
||
// 发送MQTT CONNECT消息
|
||
this.sendConnectMessage()
|
||
.then(() => {
|
||
console.log('🔧 MQTT CONNECT成功,连接建立')
|
||
this.sendLogToPage('MQTT CONNECT成功,连接建立', 'success')
|
||
this.isConnected = true
|
||
this.reconnectAttempts = 0
|
||
this.startHeartbeat()
|
||
clearTimeout(connectTimeout) // 清除超时定时器
|
||
resolve()
|
||
})
|
||
.catch((error) => {
|
||
console.error('🔧 MQTT CONNECT失败:', error)
|
||
this.sendLogToPage(`MQTT CONNECT失败: ${error.message}`, 'error')
|
||
clearTimeout(connectTimeout) // 清除超时定时器
|
||
reject(error)
|
||
})
|
||
})
|
||
|
||
this.socketTask.onMessage((res) => {
|
||
this.handleMessage(res.data)
|
||
})
|
||
|
||
this.socketTask.onError((error) => {
|
||
console.error('🔧 WebSocket错误:', error)
|
||
console.error('🔧 错误详情:', JSON.stringify(error))
|
||
this.sendLogToPage(`WebSocket错误: ${JSON.stringify(error)}`, 'error')
|
||
clearTimeout(connectTimeout) // 清除超时定时器
|
||
this.handleConnectionError(error)
|
||
reject(error)
|
||
})
|
||
|
||
this.socketTask.onClose((closeInfo) => {
|
||
console.log('🔧 WebSocket连接关闭')
|
||
console.log('🔧 关闭信息:', closeInfo)
|
||
this.sendLogToPage(`WebSocket连接关闭: ${JSON.stringify(closeInfo)}`, 'warning')
|
||
this.isConnected = false
|
||
this.stopHeartbeat()
|
||
this.attemptReconnect()
|
||
})
|
||
})
|
||
} catch (error) {
|
||
console.error('MQTT连接异常:', error)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
// 处理连接错误
|
||
handleConnectionError(error) {
|
||
this.isConnected = false
|
||
this.stopHeartbeat()
|
||
console.error('MQTT连接错误:', error)
|
||
}
|
||
|
||
// 尝试重连
|
||
attemptReconnect() {
|
||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||
this.reconnectAttempts++
|
||
console.log(`MQTT重连中... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
|
||
this.sendLogToPage(`MQTT重连中... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`, 'warning')
|
||
|
||
this.reconnectTimer = setTimeout(() => {
|
||
this.connect().catch(error => {
|
||
console.error('重连失败:', error)
|
||
this.sendLogToPage(`重连失败: ${error.message}`, 'error')
|
||
})
|
||
}, 5000)
|
||
} else {
|
||
console.error('MQTT重连次数超限,停止重连')
|
||
this.sendLogToPage('MQTT重连次数超限,停止重连', 'error')
|
||
}
|
||
}
|
||
|
||
// 发送日志到页面
|
||
sendLogToPage(message, type = 'info') {
|
||
try {
|
||
// 通过全局事件发送日志
|
||
if (typeof window !== 'undefined' && window.dispatchEvent) {
|
||
window.dispatchEvent(new CustomEvent('mqtt-log', {
|
||
detail: { message, type }
|
||
}))
|
||
}
|
||
} catch (error) {
|
||
// 忽略错误,避免影响MQTT连接
|
||
}
|
||
}
|
||
|
||
// 开始心跳
|
||
startHeartbeat() {
|
||
this.heartbeatTimer = setInterval(() => {
|
||
if (this.isConnected && this.socketTask) {
|
||
// 发送PING消息
|
||
this.sendPing()
|
||
}
|
||
}, 30000) // 30秒心跳
|
||
}
|
||
|
||
// 停止心跳
|
||
stopHeartbeat() {
|
||
if (this.heartbeatTimer) {
|
||
clearInterval(this.heartbeatTimer)
|
||
this.heartbeatTimer = null
|
||
}
|
||
}
|
||
|
||
// 发送MQTT CONNECT消息
|
||
sendConnectMessage() {
|
||
return new Promise((resolve, reject) => {
|
||
// 保存Promise的resolve和reject函数
|
||
this.connectResolve = resolve
|
||
this.connectReject = reject
|
||
try {
|
||
const options = MQTT_CONFIG.options
|
||
const clientId = options.clientId || 'uni-mqtt-client-' + Math.random().toString(16).substr(2, 8)
|
||
|
||
// 构建CONNECT消息
|
||
const protocolName = 'MQTT'
|
||
const protocolLevel = 4 // MQTT 3.1.1
|
||
|
||
// 构建CONNECT消息的各个部分
|
||
const protocolNameBytes = new TextEncoder().encode(protocolName)
|
||
const clientIdBytes = new TextEncoder().encode(clientId)
|
||
|
||
// 可变头部
|
||
let connectFlags = 0x02 // Clean Session
|
||
if (options.username) {
|
||
connectFlags |= 0x80 // Username flag
|
||
}
|
||
if (options.password) {
|
||
connectFlags |= 0x40 // Password flag
|
||
}
|
||
|
||
const variableHeaderParts = [
|
||
new Uint8Array([0x00, protocolNameBytes.length]), // 协议名长度
|
||
protocolNameBytes, // 协议名
|
||
new Uint8Array([protocolLevel]), // 协议级别
|
||
new Uint8Array([connectFlags]), // 连接标志
|
||
new Uint8Array([0x00, options.keepalive || 60]), // 保持连接时间
|
||
]
|
||
|
||
// 载荷
|
||
let payloadParts = [
|
||
new Uint8Array([0x00, clientIdBytes.length]), // 客户端ID长度
|
||
clientIdBytes, // 客户端ID
|
||
]
|
||
|
||
// 如果有用户名和密码,添加到载荷
|
||
if (options.username) {
|
||
const usernameBytes = new TextEncoder().encode(options.username)
|
||
payloadParts.push(
|
||
new Uint8Array([0x00, usernameBytes.length]), // 用户名长度
|
||
usernameBytes // 用户名
|
||
)
|
||
}
|
||
|
||
if (options.password) {
|
||
const passwordBytes = new TextEncoder().encode(options.password)
|
||
payloadParts.push(
|
||
new Uint8Array([0x00, passwordBytes.length]), // 密码长度
|
||
passwordBytes // 密码
|
||
)
|
||
}
|
||
|
||
// 合并所有部分
|
||
const allParts = [
|
||
new Uint8Array([0x10]), // CONNECT消息类型
|
||
this.encodeRemainingLength(variableHeaderParts.reduce((sum, part) => sum + part.length, 0) +
|
||
payloadParts.reduce((sum, part) => sum + part.length, 0)),
|
||
...variableHeaderParts,
|
||
...payloadParts
|
||
]
|
||
|
||
const connectMessage = this.concatUint8Arrays(allParts)
|
||
|
||
console.log('🔧 发送MQTT CONNECT消息')
|
||
console.log('🔧 CONNECT消息长度:', connectMessage.length)
|
||
console.log('🔧 CONNECT消息内容 (Hex):', Array.from(connectMessage).map(b => b.toString(16).padStart(2, '0')).join(' '))
|
||
|
||
this.socketTask.send({
|
||
data: connectMessage.buffer,
|
||
success: () => {
|
||
console.log('🔧 CONNECT消息发送成功,等待CONNACK响应...')
|
||
// 设置超时等待CONNACK
|
||
this.connackTimeout = setTimeout(() => {
|
||
console.error('🔧 等待CONNACK超时 (5秒)')
|
||
if (this.connectReject) {
|
||
this.connectReject(new Error('CONNACK timeout'))
|
||
this.connectReject = null
|
||
}
|
||
}, 5000)
|
||
},
|
||
fail: (error) => {
|
||
console.error('🔧 CONNECT消息发送失败:', error)
|
||
console.error('🔧 发送失败详情:', JSON.stringify(error))
|
||
if (this.connectReject) {
|
||
this.connectReject(error)
|
||
this.connectReject = null
|
||
}
|
||
}
|
||
})
|
||
} catch (error) {
|
||
console.error('构建CONNECT消息失败:', error)
|
||
if (this.connectReject) {
|
||
this.connectReject(error)
|
||
this.connectReject = null
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 发送PING消息
|
||
sendPing() {
|
||
try {
|
||
// MQTT PINGREQ消息 (0xC0, 0x00)
|
||
const pingMessage = new Uint8Array([0xC0, 0x00])
|
||
this.socketTask.send({
|
||
data: pingMessage.buffer
|
||
})
|
||
} catch (error) {
|
||
console.error('发送心跳失败:', error)
|
||
}
|
||
}
|
||
|
||
// 订阅主题
|
||
subscribe(topic, handler) {
|
||
if (!this.isConnected) {
|
||
console.warn('MQTT未连接,无法订阅主题:', topic)
|
||
return false
|
||
}
|
||
|
||
try {
|
||
// MQTT SUBSCRIBE消息
|
||
const messageId = this.messageId++
|
||
const topicBytes = new TextEncoder().encode(topic)
|
||
const topicLength = topicBytes.length
|
||
|
||
const payloadParts = [
|
||
new Uint8Array([0x00, topicLength]), // 主题长度
|
||
topicBytes, // 主题内容
|
||
new Uint8Array([0x00]) // QoS 0
|
||
]
|
||
|
||
const payload = this.concatUint8Arrays(payloadParts)
|
||
const remainingLength = 2 + payload.length // messageId + payload
|
||
|
||
const subscribeMessage = this.concatUint8Arrays([
|
||
new Uint8Array([0x82]), // SUBSCRIBE消息类型
|
||
this.encodeRemainingLength(remainingLength),
|
||
new Uint8Array([(messageId >> 8) & 0xFF, messageId & 0xFF]), // messageId
|
||
payload
|
||
])
|
||
|
||
console.log('📤 发送MQTT SUBSCRIBE消息')
|
||
console.log('订阅主题:', topic)
|
||
console.log('消息ID:', messageId)
|
||
|
||
this.socketTask.send({
|
||
data: subscribeMessage.buffer
|
||
})
|
||
|
||
this.subscriptions.set(topic, true)
|
||
this.messageHandlers.set(topic, handler)
|
||
console.log('订阅主题成功:', topic)
|
||
return true
|
||
} catch (error) {
|
||
console.error('订阅主题失败:', topic, error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 取消订阅
|
||
unsubscribe(topic) {
|
||
if (this.subscriptions.has(topic)) {
|
||
try {
|
||
// MQTT UNSUBSCRIBE消息
|
||
const messageId = this.messageId++
|
||
const topicLength = Buffer.byteLength(topic, 'utf8')
|
||
const payload = Buffer.concat([
|
||
Buffer.from([0x00, topicLength]), // 主题长度
|
||
Buffer.from(topic, 'utf8') // 主题内容
|
||
])
|
||
|
||
const remainingLength = 2 + payload.length // messageId + payload
|
||
const unsubscribeMessage = Buffer.concat([
|
||
Buffer.from([0xA2]), // UNSUBSCRIBE消息类型
|
||
this.encodeRemainingLength(remainingLength),
|
||
Buffer.from([(messageId >> 8) & 0xFF, messageId & 0xFF]), // messageId
|
||
payload
|
||
])
|
||
|
||
this.socketTask.send({
|
||
data: unsubscribeMessage.buffer
|
||
})
|
||
|
||
this.subscriptions.delete(topic)
|
||
this.messageHandlers.delete(topic)
|
||
console.log('取消订阅主题:', topic)
|
||
} catch (error) {
|
||
console.error('取消订阅失败:', topic, error)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 发布消息
|
||
publish(topic, message) {
|
||
if (!this.isConnected) {
|
||
console.warn('MQTT未连接,无法发布消息')
|
||
return false
|
||
}
|
||
|
||
try {
|
||
const payload = typeof message === 'object' ? JSON.stringify(message) : message
|
||
const topicLength = Buffer.byteLength(topic, 'utf8')
|
||
const payloadBuffer = Buffer.from(payload, 'utf8')
|
||
|
||
const remainingLength = 2 + topicLength + payloadBuffer.length
|
||
const publishMessage = Buffer.concat([
|
||
Buffer.from([0x30]), // PUBLISH消息类型
|
||
this.encodeRemainingLength(remainingLength),
|
||
Buffer.from([(topicLength >> 8) & 0xFF, topicLength & 0xFF]), // 主题长度
|
||
Buffer.from(topic, 'utf8'), // 主题内容
|
||
payloadBuffer // 消息内容
|
||
])
|
||
|
||
this.socketTask.send({
|
||
data: publishMessage.buffer
|
||
})
|
||
|
||
console.log('发布消息成功:', topic, payload)
|
||
return true
|
||
} catch (error) {
|
||
console.error('发布消息失败:', topic, error)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 处理接收到的消息
|
||
handleMessage(data) {
|
||
try {
|
||
const buffer = new Uint8Array(data)
|
||
const messageType = (buffer[0] >> 4) & 0x0F
|
||
const flags = buffer[0] & 0x0F
|
||
|
||
// 打印协议内容
|
||
console.log('📦 收到MQTT协议消息:')
|
||
console.log('消息类型:', messageType, this.getMessageTypeName(messageType))
|
||
|
||
if (messageType === 2) { // CONNACK消息
|
||
this.handleConnackMessage(buffer)
|
||
} else if (messageType === 3) { // PUBLISH消息
|
||
this.handlePublishMessage(buffer)
|
||
} else if (messageType === 9) { // SUBACK消息
|
||
console.log('订阅确认收到')
|
||
} else if (messageType === 13) { // PINGRESP消息
|
||
console.log('心跳响应收到')
|
||
} else {
|
||
console.log('收到未知消息类型:', messageType)
|
||
}
|
||
} catch (error) {
|
||
console.error('处理消息失败:', error)
|
||
}
|
||
}
|
||
|
||
// 获取消息类型名称
|
||
getMessageTypeName(type) {
|
||
const typeNames = {
|
||
0: 'RESERVED',
|
||
1: 'CONNECT',
|
||
2: 'CONNACK',
|
||
3: 'PUBLISH',
|
||
4: 'PUBACK',
|
||
5: 'PUBREC',
|
||
6: 'PUBREL',
|
||
7: 'PUBCOMP',
|
||
8: 'SUBSCRIBE',
|
||
9: 'SUBACK',
|
||
10: 'UNSUBSCRIBE',
|
||
11: 'UNSUBACK',
|
||
12: 'PINGREQ',
|
||
13: 'PINGRESP',
|
||
14: 'DISCONNECT',
|
||
15: 'RESERVED'
|
||
}
|
||
return typeNames[type] || 'UNKNOWN'
|
||
}
|
||
|
||
// 处理CONNACK消息
|
||
handleConnackMessage(buffer) {
|
||
try {
|
||
console.log('🔧 收到CONNACK消息')
|
||
|
||
if (buffer.length >= 4) {
|
||
const returnCode = buffer[3]
|
||
console.log('🔧 CONNACK返回码:', returnCode)
|
||
|
||
if (returnCode === 0) {
|
||
console.log('🔧 ✅ MQTT连接成功')
|
||
this.isConnected = true
|
||
this.reconnectAttempts = 0
|
||
// 清除超时定时器
|
||
if (this.connackTimeout) {
|
||
clearTimeout(this.connackTimeout)
|
||
this.connackTimeout = null
|
||
}
|
||
// 触发连接成功的Promise resolve
|
||
if (this.connectResolve) {
|
||
this.connectResolve()
|
||
this.connectResolve = null
|
||
}
|
||
} else {
|
||
console.error('🔧 ❌ MQTT连接被拒绝,返回码:', returnCode)
|
||
const errorMessages = {
|
||
1: '连接被拒绝,不支持的协议版本',
|
||
2: '连接被拒绝,不合格的客户端标识符',
|
||
3: '连接被拒绝,服务端不可用',
|
||
4: '连接被拒绝,无效的用户名或密码',
|
||
5: '连接被拒绝,未授权'
|
||
}
|
||
console.error('🔧 错误原因:', errorMessages[returnCode] || '未知错误')
|
||
this.isConnected = false
|
||
// 清除超时定时器
|
||
if (this.connackTimeout) {
|
||
clearTimeout(this.connackTimeout)
|
||
this.connackTimeout = null
|
||
}
|
||
// 触发连接失败的Promise reject
|
||
if (this.connectReject) {
|
||
this.connectReject(new Error(`MQTT连接被拒绝: ${errorMessages[returnCode] || '未知错误'}`))
|
||
this.connectReject = null
|
||
}
|
||
}
|
||
} else {
|
||
console.error('🔧 CONNACK消息长度不足:', buffer.length)
|
||
}
|
||
} catch (error) {
|
||
console.error('🔧 解析CONNACK消息失败:', error)
|
||
}
|
||
}
|
||
|
||
// 处理PUBLISH消息
|
||
handlePublishMessage(buffer) {
|
||
try {
|
||
let offset = 1
|
||
|
||
// 解析剩余长度
|
||
const { length, bytesRead } = this.decodeRemainingLength(buffer, offset)
|
||
offset += bytesRead
|
||
|
||
console.log('🔍 PUBLISH消息解析:')
|
||
console.log('剩余长度:', length)
|
||
|
||
// 解析主题长度
|
||
const topicLength = (buffer[offset] << 8) | buffer[offset + 1]
|
||
offset += 2
|
||
|
||
console.log('主题长度:', topicLength)
|
||
|
||
// 解析主题
|
||
const topic = new TextDecoder().decode(buffer.slice(offset, offset + topicLength))
|
||
offset += topicLength
|
||
|
||
console.log('主题名称:', topic)
|
||
|
||
// 解析消息内容
|
||
const messageBytes = buffer.slice(offset)
|
||
console.log('🔍 消息字节数据:')
|
||
console.log('字节长度:', messageBytes.length)
|
||
console.log('字节数据 (Hex):', Array.from(messageBytes).map(b => b.toString(16).padStart(2, '0')).join(' '))
|
||
console.log('字节数据 (Dec):', Array.from(messageBytes).join(' '))
|
||
|
||
// 尝试不同的编码方式
|
||
let messageData
|
||
try {
|
||
// 首先尝试UTF-8解码
|
||
messageData = new TextDecoder('utf-8').decode(messageBytes)
|
||
console.log('✅ UTF-8解码成功')
|
||
} catch (error) {
|
||
console.log('❌ UTF-8解码失败,尝试其他编码')
|
||
try {
|
||
// 尝试ASCII解码
|
||
messageData = new TextDecoder('ascii').decode(messageBytes)
|
||
console.log('✅ ASCII解码成功')
|
||
} catch (error2) {
|
||
console.log('❌ ASCII解码失败,使用原始字节')
|
||
messageData = Array.from(messageBytes).map(b => String.fromCharCode(b)).join('')
|
||
}
|
||
}
|
||
|
||
console.log('📨 收到MQTT消息:')
|
||
console.log('主题:', topic)
|
||
console.log('解码后消息:', messageData)
|
||
console.log('消息内容 (Hex):', Array.from(new TextEncoder().encode(messageData)).map(b => b.toString(16).padStart(2, '0')).join(' '))
|
||
|
||
// 数据类型判断和JSON转换
|
||
console.log('🔍 数据类型分析:')
|
||
console.log('数据类型:', typeof messageData)
|
||
|
||
// 尝试解析JSON
|
||
try {
|
||
console.log('🔄 尝试解析JSON...')
|
||
|
||
const jsonData = JSON.parse(messageData)
|
||
console.log('✅ JSON解析成功!')
|
||
console.log('解析后的数据类型:', typeof jsonData)
|
||
|
||
// 如果是数组,显示数组信息
|
||
if (Array.isArray(jsonData)) {
|
||
console.log('📋 数组信息:')
|
||
console.log('数组长度:', jsonData.length)
|
||
jsonData.forEach((item, index) => {
|
||
console.log(`数组[${index}]:`, typeof item, item)
|
||
})
|
||
}
|
||
|
||
// 如果是对象,显示对象信息
|
||
if (jsonData && typeof jsonData === 'object' && !Array.isArray(jsonData)) {
|
||
console.log('📋 对象信息:')
|
||
console.log('对象键:', Object.keys(jsonData))
|
||
Object.entries(jsonData).forEach(([key, value]) => {
|
||
console.log(`${key}:`, typeof value, value)
|
||
})
|
||
}
|
||
|
||
} catch (jsonError) {
|
||
console.log('❌ JSON解析失败:', jsonError.message)
|
||
console.log('原始数据可能不是有效的JSON格式')
|
||
}
|
||
|
||
// 解析设备数据并调用订阅的回调函数
|
||
console.log('🔍 开始解析设备数据...')
|
||
const parsedData = DataParser.parseDeviceData(messageData)
|
||
if (parsedData) {
|
||
console.log('✅ 设备数据解析成功')
|
||
// 调用订阅的回调函数
|
||
const handler = this.messageHandlers.get(topic)
|
||
if (handler) {
|
||
try {
|
||
handler(parsedData)
|
||
} catch (error) {
|
||
console.error('❌ 消息处理器执行失败:', error)
|
||
}
|
||
} else {
|
||
console.log('⚠️ 未找到主题处理器:', topic)
|
||
}
|
||
} else {
|
||
console.log('❌ 设备数据解析失败或数据为空')
|
||
}
|
||
} catch (error) {
|
||
console.error('解析PUBLISH消息失败:', error)
|
||
}
|
||
}
|
||
|
||
// 合并Uint8Array数组
|
||
concatUint8Arrays(arrays) {
|
||
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0)
|
||
const result = new Uint8Array(totalLength)
|
||
let offset = 0
|
||
for (const arr of arrays) {
|
||
result.set(arr, offset)
|
||
offset += arr.length
|
||
}
|
||
return result
|
||
}
|
||
|
||
// 编码剩余长度
|
||
encodeRemainingLength(length) {
|
||
const bytes = []
|
||
do {
|
||
let byte = length % 128
|
||
length = Math.floor(length / 128)
|
||
if (length > 0) {
|
||
byte |= 0x80
|
||
}
|
||
bytes.push(byte)
|
||
} while (length > 0)
|
||
return new Uint8Array(bytes)
|
||
}
|
||
|
||
// 解码剩余长度
|
||
decodeRemainingLength(buffer, offset) {
|
||
let multiplier = 1
|
||
let value = 0
|
||
let bytesRead = 0
|
||
let byte
|
||
|
||
do {
|
||
if (offset + bytesRead >= buffer.length) {
|
||
throw new Error('Invalid remaining length')
|
||
}
|
||
byte = buffer[offset + bytesRead]
|
||
value += (byte & 0x7F) * multiplier
|
||
multiplier *= 128
|
||
bytesRead++
|
||
} while ((byte & 0x80) !== 0)
|
||
|
||
return { length: value, bytesRead }
|
||
}
|
||
|
||
// 获取连接状态
|
||
getConnectionStatus() {
|
||
return {
|
||
isConnected: this.isConnected,
|
||
reconnectAttempts: this.reconnectAttempts,
|
||
subscriptions: Array.from(this.subscriptions.keys())
|
||
}
|
||
}
|
||
|
||
// 断开连接
|
||
disconnect() {
|
||
if (this.socketTask) {
|
||
this.socketTask.close()
|
||
this.isConnected = false
|
||
this.subscriptions.clear()
|
||
this.messageHandlers.clear()
|
||
this.stopHeartbeat()
|
||
|
||
if (this.reconnectTimer) {
|
||
clearTimeout(this.reconnectTimer)
|
||
this.reconnectTimer = null
|
||
}
|
||
|
||
console.log('MQTT连接已断开')
|
||
}
|
||
}
|
||
}
|
||
|
||
export default new UniMqttClient() |