20260905:微信小程序-蓝牙采集网关-mqtt连接

This commit is contained in:
zhugongping 2026-09-05 23:15:00 +08:00
parent 7ce0fdb147
commit 7b611a1334
4 changed files with 577 additions and 4 deletions

View File

@ -5,8 +5,9 @@
// 开发环境
const DEV_CONFIG = {
baseUrl: 'http://192.168.110.117:8080',
mqttServer: 'wxs://192.168.110.117:8084/mqtt',
baseUrl: 'http://192.168.100.199:8080',
// 使用阿里云 EMQX 的非加密 WebSocket 端口
mqttServer: 'ws://47.121.180.229:8083/mqtt',
};
// 生产环境

View File

@ -0,0 +1,573 @@
/**
* 微信小程序 MQTT 适配库 v2.0
* 修复 WebSocket 连接问题,增加调试日志
*/
!function(root) {
'use strict';
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
root.mqtt = factory();
}
function factory() {
// 微信小程序 WebSocket 适配器
class WxWebSocket {
constructor(url, protocols) {
console.log('[WxWebSocket] 初始化:', url, protocols);
this.url = url;
this.protocols = protocols;
this.readyState = 0; // CONNECTING
this._socket = null;
this.onopen = null;
this.onclose = null;
this.onerror = null;
this.onmessage = null;
this._connect();
}
_connect() {
const that = this;
console.log('[WxWebSocket] 开始连接:', this.url);
try {
// 使用 SocketTask 进行连接
const socketTask = wx.connectSocket({
url: this.url,
protocols: this.protocols ? [this.protocols] : [],
success: () => {
console.log('[WxWebSocket] 连接请求已发送');
},
fail: (err) => {
console.error('[WxWebSocket] 连接失败:', err);
that.readyState = 3;
that.onerror && that.onerror(new Error('WebSocket connection failed: ' + JSON.stringify(err)));
}
});
this._socket = socketTask;
// 监听特定连接的事件
socketTask.onOpen((res) => {
console.log('[WxWebSocket] 连接已打开');
that.readyState = 1; // OPEN
that.onopen && that.onopen({ type: 'open' });
});
socketTask.onMessage((res) => {
console.log('[WxWebSocket] 收到消息, 数据类型:', typeof res.data);
if (that.onmessage) {
let data = res.data;
if (typeof data === 'string') {
data = that._stringToArrayBuffer(data);
}
that.onmessage({ data });
}
});
socketTask.onError((err) => {
console.error('[WxWebSocket] 连接错误:', err);
that.readyState = 3;
that.onerror && that.onerror(new Error('WebSocket error: ' + JSON.stringify(err)));
});
socketTask.onClose((res) => {
console.log('[WxWebSocket] 连接关闭:', res);
that.readyState = 3;
that.onclose && that.onclose(res);
});
} catch (err) {
console.error('[WxWebSocket] 创建连接异常:', err);
that.readyState = 3;
that.onerror && that.onerror(err);
}
}
_stringToArrayBuffer(str) {
const buffer = new ArrayBuffer(str.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < str.length; i++) {
view[i] = str.charCodeAt(i);
}
return buffer;
}
send(data, callback) {
if (this.readyState !== 1) {
console.error('[WxWebSocket] 发送失败: 连接未打开, readyState:', this.readyState);
callback && callback(new Error('WebSocket is not open'));
return;
}
try {
let sendData = data;
if (data instanceof ArrayBuffer) {
sendData = data;
} else if (typeof data === 'string') {
sendData = data;
}
console.log('[WxWebSocket] 发送数据, 大小:', sendData.byteLength || sendData.length);
this._socket.send({
data: sendData,
success: () => {
console.log('[WxWebSocket] 发送成功');
callback && callback();
},
fail: (err) => {
console.error('[WxWebSocket] 发送失败:', err);
callback && callback(new Error('Send failed: ' + JSON.stringify(err)));
}
});
} catch (err) {
console.error('[WxWebSocket] 发送异常:', err);
callback && callback(err);
}
}
close() {
console.log('[WxWebSocket] 关闭连接, readyState:', this.readyState);
if (this.readyState === 3) return;
this.readyState = 2; // CLOSING
this._socket.close({
success: () => {
console.log('[WxWebSocket] 关闭成功');
}
});
}
}
// MQTT 客户端
class MqttClient {
constructor(url, options) {
console.log('[MqttClient] 初始化客户端, URL:', url, 'Options:', options);
this.url = url;
this.options = options || {};
this.ws = null;
this.connected = false;
this.messageId = 1;
this.subscriptions = new Map();
this.onConnect = null;
this.onMessage = null;
this.onError = null;
this.onClose = null;
this.onReconnect = null;
// 连接参数
this.clientId = options.clientId || 'mqtt_' + Math.random().toString(16).substr(2, 8);
this.username = options.username;
this.password = options.password;
this.keepalive = options.keepalive || 60;
this.clean = options.clean !== false;
this.reconnectPeriod = options.reconnectPeriod || 5000;
this.connectTimeout = options.connectTimeout || 30000;
this._connectTimer = null;
this._reconnectTimer = null;
this._keepaliveTimer = null;
}
connect() {
return new Promise((resolve, reject) => {
console.log('[MqttClient] 开始连接, 超时时间:', this.connectTimeout);
const timeoutId = setTimeout(() => {
if (!this.connected) {
console.error('[MqttClient] 连接超时');
this.ws && this.ws.close();
reject(new Error('Connection timeout'));
}
}, this.connectTimeout);
try {
this.ws = new WxWebSocket(this.url, 'mqtt');
this.ws.onopen = () => {
console.log('[MqttClient] WebSocket 已打开,发送 MQTT CONNECT');
clearTimeout(timeoutId);
this._sendConnect();
};
this.ws.onmessage = (event) => {
this._handleMessage(event.data);
};
this.ws.onerror = (err) => {
console.error('[MqttClient] WebSocket 错误:', err);
clearTimeout(timeoutId);
this.connected = false;
this.onError && this.onError(err);
reject(err);
};
this.ws.onclose = () => {
console.log('[MqttClient] WebSocket 关闭');
clearTimeout(timeoutId);
this.connected = false;
this.onClose && this.onClose();
this._scheduleReconnect();
};
} catch (err) {
console.error('[MqttClient] 创建 WebSocket 异常:', err);
clearTimeout(timeoutId);
reject(err);
}
});
}
_sendConnect() {
console.log('[MqttClient] 构建 CONNECT 包');
const packet = this._buildConnectPacket();
console.log('[MqttClient] CONNECT 包大小:', packet.byteLength, 'bytes');
this.ws.send(packet, (err) => {
if (err) {
console.error('[MqttClient] 发送 CONNECT 失败:', err);
this.onError && this.onError(err);
} else {
console.log('[MqttClient] CONNECT 包已发送,等待 CONNACK');
}
});
}
_buildConnectPacket() {
const protocolId = 'MQTT';
const protocolVersion = 4;
const clientId = this.clientId;
// Flags: Clean Session + Username + Password
let flags = 0x02; // Clean Session
if (this.username) flags |= 0x80;
if (this.password) flags |= 0x40;
const keepalive = this.keepalive;
// 构建 Variable Header
const variableHeader = [
...this._encodeString(protocolId),
protocolVersion,
flags,
(keepalive >> 8) & 0xFF,
keepalive & 0xFF
];
// 构建 Payload
let payload = this._encodeString(clientId);
if (this.username) {
payload = payload.concat(this._encodeString(this.username));
}
if (this.password) {
payload = payload.concat(this._encodeString(this.password));
}
// 构建 Fixed Header
const remainingLength = variableHeader.length + payload.length;
const fixedHeader = [0x10, ...this._encodeRemainingLength(remainingLength)];
// 合并为一个数组
const packet = fixedHeader.concat(variableHeader).concat(payload);
// 转换为 ArrayBuffer
return new Uint8Array(packet).buffer;
}
_encodeString(str) {
const length = str.length;
const bytes = [];
bytes.push((length >> 8) & 0xFF);
bytes.push(length & 0xFF);
for (let i = 0; i < str.length; i++) {
bytes.push(str.charCodeAt(i));
}
return bytes;
}
_encodeRemainingLength(length) {
const result = [];
let x = length;
do {
let byte = x % 128;
x = Math.floor(x / 128);
if (x > 0) byte |= 0x80;
result.push(byte);
} while (x > 0);
return result;
}
_handleMessage(data) {
const view = new Uint8Array(data);
const type = view[0] >> 4;
console.log('[MqttClient] 收到消息, 类型:', type, '长度:', view.length);
switch (type) {
case 2: // CONNACK
this._handleConnack(view);
break;
case 3: // PUBLISH
this._handlePublish(view);
break;
case 4: // PUBACK
console.log('[MqttClient] 收到 PUBACK');
break;
case 9: // SUBACK
console.log('[MqttClient] 收到 SUBACK');
break;
case 13: // PINGRESP
console.log('[MqttClient] 收到 PINGRESP');
break;
}
}
_handleConnack(view) {
console.log('[MqttClient] 收到 CONNACK, 长度:', view.length);
const flags = view[2];
const returnCode = view[3];
console.log('[MqttClient] CONNACK flags:', flags, 'returnCode:', returnCode);
if (returnCode === 0) {
this.connected = true;
console.log('[MqttClient] 连接成功');
this._startKeepalive();
this.onConnect && this.onConnect();
} else {
const errors = [
'',
'Unacceptable protocol version',
'Identifier rejected',
'Server unavailable',
'Bad username or password',
'Not authorized'
];
const errorMsg = errors[returnCode] || 'Unknown error';
console.error('[MqttClient] 连接被拒绝:', errorMsg);
this.onError && this.onError(new Error('Connection refused: ' + errorMsg));
}
}
_handlePublish(view) {
console.log('[MqttClient] 收到 PUBLISH 消息');
let pos = 2;
const topicLength = (view[pos] << 8) | view[pos + 1];
pos += 2;
const topic = String.fromCharCode(...view.slice(pos, pos + topicLength));
pos += topicLength;
const qos = (view[0] >> 1) & 0x03;
if (qos > 0) pos += 2; // 跳过 messageId
const payload = view.slice(pos);
console.log('[MqttClient] 主题:', topic, '载荷长度:', payload.length);
this.onMessage && this.onMessage(topic, {
payload: payload,
toString: () => String.fromCharCode(...payload)
});
}
_startKeepalive() {
console.log('[MqttClient] 启动心跳, 间隔:', this.keepalive, '秒');
if (this._keepaliveTimer) clearInterval(this._keepaliveTimer);
this._keepaliveTimer = setInterval(() => {
if (this.connected) {
this._sendPing();
}
}, this.keepalive * 1000);
}
_sendPing() {
console.log('[MqttClient] 发送 PINGREQ');
const packet = new Uint8Array([0xC0, 0x00]);
this.ws.send(packet.buffer);
}
subscribe(topic, options, callback) {
if (typeof options === 'function') {
callback = options;
options = { qos: 0 };
}
console.log('[MqttClient] 订阅主题:', topic, 'QoS:', options.qos);
const qos = options.qos || 0;
const messageId = this.messageId++;
// Variable Header: Message ID
const variableHeader = [
(messageId >> 8) & 0xFF,
messageId & 0xFF
];
// Payload: Topic Filter + QoS
const topicData = this._encodeString(topic);
const payload = topicData.concat([qos]);
// Fixed Header
const remainingLength = variableHeader.length + payload.length;
const fixedHeader = [0x82, ...this._encodeRemainingLength(remainingLength)];
const packet = fixedHeader.concat(variableHeader).concat(payload);
this.subscriptions.set(topic, callback);
this.ws.send(new Uint8Array(packet).buffer, (err) => {
if (err) {
console.error('[MqttClient] 订阅失败:', err);
this.onError && this.onError(err);
} else {
console.log('[MqttClient] 订阅包已发送');
}
});
}
publish(topic, message, options, callback) {
if (typeof options === 'function') {
callback = options;
options = { qos: 0 };
}
console.log('[MqttClient] 发布消息, 主题:', topic, 'QoS:', options.qos);
const qos = options.qos || 0;
const retain = options.retain ? 1 : 0;
const dup = 0;
let flags = (dup << 3) | (qos << 1) | retain;
let messageId = 0;
if (qos > 0) {
messageId = this.messageId++;
}
// Topic
const topicData = this._encodeString(topic);
let variableHeader = topicData;
if (qos > 0) {
variableHeader = variableHeader.concat([
(messageId >> 8) & 0xFF,
messageId & 0xFF
]);
}
// Payload
let payload = [];
if (typeof message === 'string') {
for (let i = 0; i < message.length; i++) {
payload.push(message.charCodeAt(i));
}
} else if (message instanceof ArrayBuffer) {
payload = Array.from(new Uint8Array(message));
}
// Fixed Header
const remainingLength = variableHeader.length + payload.length;
const fixedHeader = [0x30 | flags, ...this._encodeRemainingLength(remainingLength)];
const packet = fixedHeader.concat(variableHeader).concat(payload);
this.ws.send(new Uint8Array(packet).buffer, (err) => {
callback && callback(err);
});
}
unsubscribe(topic, callback) {
console.log('[MqttClient] 取消订阅:', topic);
const messageId = this.messageId++;
const topicData = this._encodeString(topic);
const variableHeader = [
(messageId >> 8) & 0xFF,
messageId & 0xFF
];
const remainingLength = variableHeader.length + topicData.length;
const fixedHeader = [0xA2, ...this._encodeRemainingLength(remainingLength)];
const packet = fixedHeader.concat(variableHeader).concat(topicData);
this.subscriptions.delete(topic);
this.ws.send(new Uint8Array(packet).buffer, (err) => {
callback && callback(err);
});
}
end(force, callback) {
if (typeof force === 'function') {
callback = force;
force = false;
}
console.log('[MqttClient] 断开连接');
if (this._keepaliveTimer) clearInterval(this._keepaliveTimer);
if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
this.connected = false;
// 发送 DISCONNECT 包
const packet = new Uint8Array([0xE0, 0x00]);
this.ws.send(packet.buffer);
setTimeout(() => {
this.ws.close();
callback && callback();
}, 100);
}
_scheduleReconnect() {
if (this.reconnectPeriod > 0 && !this._reconnectTimer) {
console.log('[MqttClient] 计划重连, 延迟:', this.reconnectPeriod);
this._reconnectTimer = setTimeout(() => {
this._reconnectTimer = null;
this.onReconnect && this.onReconnect();
this.connect().catch(err => {
console.error('[MqttClient] 重连失败:', err);
this.onError && this.onError(err);
});
}, this.reconnectPeriod);
}
}
on(event, callback) {
switch (event) {
case 'connect':
this.onConnect = callback;
break;
case 'message':
this.onMessage = callback;
break;
case 'error':
this.onError = callback;
break;
case 'close':
this.onClose = callback;
break;
case 'reconnect':
this.onReconnect = callback;
break;
}
}
}
// 导出连接函数
function connect(url, options) {
console.log('[MQTT] 创建客户端连接:', url);
const client = new MqttClient(url, options);
client.connect();
return client;
}
return {
connect,
MqttClient
};
}
}(typeof self !== 'undefined' ? self : this);

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
/**
* MQTT 客户端服务
*/
const mqtt = require('../libs/mqtt.min.js');
const mqtt = require('../libs/mqtt-wx.min.js');
const envConfig = require('../env.config.js');
// MQTT 状态常量