20260905:微信小程序-蓝牙采集网关-mqtt连接
This commit is contained in:
parent
7ce0fdb147
commit
7b611a1334
@ -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',
|
||||
};
|
||||
|
||||
// 生产环境
|
||||
|
||||
573
yack-mini/miniprogram/libs/mqtt-wx.min.js
vendored
Normal file
573
yack-mini/miniprogram/libs/mqtt-wx.min.js
vendored
Normal 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);
|
||||
1
yack-mini/miniprogram/libs/mqtt.min.js
vendored
1
yack-mini/miniprogram/libs/mqtt.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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 状态常量
|
||||
|
||||
Loading…
Reference in New Issue
Block a user