Files
uni-pda/utils/BluetoothScaleService.new.js
T
2026-06-27 20:58:16 +08:00

1052 lines
25 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.
/**
* 蓝牙称重设备服务模块(新版:支持智能数据发送与健壮连接管理)
* 功能:
* - 自动连接已配置的称重设备
* - 主动读取重量:发送 'R' 命令,设备返回一次重量数据(readWeight方法)
* - 实时监听并解析重量数据(支持ASCII/二进制协议)
* - 发送控制命令(去皮、校准、单位切换等)
* - 连接状态管理与事件通知
* - 支持从 Vuex 或 localStorage 加载配置
*
* 使用方法:
* import scaleService from '@/utils/BluetoothScaleService.new.js';
* await scaleService.autoConnect();
*
* // 主动读取重量(发送一次命令,返回一次数据)
* const result = await scaleService.readWeight();
* console.log(result.raw); // 十六进制原始数据
* console.log(result.rawAscii); // ASCII原始数据
* console.log(result.value); // 解析后的重量值
*
* // 监听重量变化(设备主动推送模式)
* scaleService.onWeightChange(this.handleWeightChange);
*/
let store = null;
try {
store = require('@/store/index.js').default;
} catch (e) {
console.warn('[称重服务] Vuex store 加载失败,将使用 localStorage');
}
class BluetoothScaleService {
constructor() {
this.adapterOpen = false;
this.isConnected = false;
this.connecting = false;
this.deviceId = null;
this.deviceName = '';
this.serviceId = null;
this.notifyCharId = null;
this.writeCharId = null;
this.pendingRead = null;
this.readTimeout = 5000;
this.connectionChangeCallbacks = [];
this.weightChangeCallbacks = [];
this.errorCallbacks = [];
this.logCallbacks = [];
this._connectionListener = null;
this._dataListener = null;
this._writeType = null;
this._mtu = 20;
this.currentWeight = {
value: 0,
unit: 'kg',
stable: false,
raw: null,
timestamp: null
};
this.protocolType = 'auto';
this.protocols = {
ascii: {
pattern: /^([+-]?\d+\.?\d*)\s*(kg|g|lb|oz)?$/,
parser: (str) => {
const match = str.match(/([+-]?\d+\.?\d*)/);
const unitMatch = str.match(/(kg|g|lb|oz)/i);
return {
value: match ? parseFloat(match[1]) : null,
unit: unitMatch ? unitMatch[1].toLowerCase() : 'kg'
};
}
},
A12: {
parser: (buffer) => {
if (!buffer || buffer.length < 5) return null;
const data = new Uint8Array(buffer);
const sign = (data[0] & 0x80) ? -1 : 1;
const stable = (data[0] & 0x40) !== 0;
const decimal = (data[0] & 0x30) >> 4;
const weight = ((data[1] << 8) | data[2]) & 0x3FFF;
const value = sign * weight / Math.pow(10, decimal);
const units = ['kg', 'g', 'lb', 'oz'];
const unitIndex = data[3] & 0x03;
return {
value: value,
unit: units[unitIndex] || 'kg',
stable: stable
};
}
},
Sartorius: {
parser: (buffer) => {
if (!buffer || buffer.length < 6) return null;
const data = new Uint8Array(buffer);
const value = ((data[2] << 16) | (data[3] << 8) | data[4]) / 1000;
return {
value: value,
unit: 'kg',
stable: data[1] === 0x01
};
}
},
Mettler: {
parser: (buffer) => {
if (!buffer || buffer.length < 8) return null;
const data = new Uint8Array(buffer);
const value = parseFloat(String.fromCharCode.apply(null, data.slice(1, 7)));
return {
value: value,
unit: 'kg',
stable: data[7] === 0x0D
};
}
}
};
this.commands = {
// 读取重量命令:0x52 = ASCII 'R'
READ_WEIGHT: [0x52],
TARE: [0x1B, 0x54],
CALIBRATE_1KG: [0x1B, 0x43, 0x01],
CALIBRATE_2KG: [0x1B, 0x43, 0x02],
CALIBRATE_5KG: [0x1B, 0x43, 0x03],
UNIT_KG: [0x1B, 0x55, 0x01],
UNIT_G: [0x1B, 0x55, 0x02],
UNIT_LB: [0x1B, 0x55, 0x03],
ZERO: [0x1B, 0x5A],
RESET: [0x1B, 0x52]
};
}
/**
* 初始化蓝牙适配器
*/
async init() {
if (this.adapterOpen) return;
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: () => {
this.adapterOpen = true;
this._registerListeners();
resolve();
},
fail: (err) => {
console.error('[称重服务] 蓝牙初始化失败', err);
reject(err);
}
});
});
}
/**
* 注册蓝牙事件监听
*/
_registerListeners() {
if (this._connectionListener) return;
this._connectionListener = (res) => {
if (!res.connected && this.deviceId === res.deviceId) {
this.isConnected = false;
this._log('设备连接断开');
this._notifyConnectionChange(false);
}
};
this._dataListener = (res) => {
if (res.deviceId === this.deviceId) {
this._handleDataReceived(res.value);
}
};
uni.onBLEConnectionStateChange(this._connectionListener);
uni.onBLECharacteristicValueChange(this._dataListener);
}
/**
* 销毁,清理资源
*/
destroy() {
if (this._connectionListener) {
try {
uni.offBLEConnectionStateChange(this._connectionListener);
} catch (e) {
console.warn('[称重服务] 移除连接监听失败', e);
}
this._connectionListener = null;
}
if (this._dataListener) {
try {
uni.offBLECharacteristicValueChange(this._dataListener);
} catch (e) {
console.warn('[称重服务] 移除数据监听失败', e);
}
this._dataListener = null;
}
if (this.pendingRead) {
clearTimeout(this.pendingRead.timer);
this.pendingRead.reject(new Error('服务已销毁'));
this.pendingRead = null;
}
this.disconnect();
}
/**
* 处理接收到的数据
*/
_handleDataReceived(buffer) {
try {
if (this.pendingRead) {
const pending = this.pendingRead;
this.pendingRead = null;
clearTimeout(pending.timer);
const rawHex = this._bufferToHex(buffer);
const rawAscii = this._bufferToString(buffer);
this._log(`读取响应 - HEX: ${rawHex}, ASCII: ${rawAscii}`);
try {
const weight = this._parseCustom(buffer);
pending.resolve({
raw: rawHex,
rawAscii: rawAscii,
...weight
});
} catch (parseError) {
this._log(`解析响应失败: ${parseError.message}`, 'error');
pending.resolve({
raw: rawHex,
rawAscii: rawAscii,
value: null,
unit: 'kg',
stable: false,
rawHex: rawHex,
rawAscii: rawAscii.trim()
});
}
return;
}
let weight = null;
if (this.protocolType === 'auto') {
weight = this._autoParse(buffer);
} else if (this.protocolType === 'ascii') {
weight = this._parseAscii(buffer);
} else {
weight = this._parseBinary(buffer);
}
if (weight) {
this.currentWeight = {
...weight,
raw: this._bufferToHex(buffer),
timestamp: new Date().toISOString()
};
this._log(`重量: ${weight.value} ${weight.unit} ${weight.stable ? '(稳定)' : '(变化中)'}`);
this._notifyWeightChange(this.currentWeight);
}
} catch (error) {
this._log(`数据解析错误: ${error.message}`, 'error');
this._notifyError(error);
}
}
/**
* 自动检测并解析数据格式
*/
_autoParse(buffer) {
const asciiResult = this._parseAscii(buffer);
if (asciiResult) {
return asciiResult;
}
return this._parseBinary(buffer);
}
/**
* 解析ASCII格式数据
*/
_parseAscii(buffer) {
try {
const text = this._bufferToString(buffer);
const trimmed = text.trim();
const match = trimmed.match(/^([+-]?\d+\.?\d*)\s*(kg|g|lb|oz)?$/i);
if (match) {
return {
value: parseFloat(match[1]),
unit: match[2] ? match[2].toLowerCase() : 'kg',
stable: true
};
}
const statusMatch = trimmed.match(/^(ST|US|OL),?([+-]?\d+\.?\d*)\s*(kg|g|lb|oz)?$/i);
if (statusMatch) {
return {
value: parseFloat(statusMatch[2]),
unit: statusMatch[3] ? statusMatch[3].toLowerCase() : 'kg',
stable: statusMatch[1].toUpperCase() === 'ST'
};
}
// 自定义格式: wn0000.00kg (wn前缀 + 数字 + 单位)
const customMatch = trimmed.match(/^[a-zA-Z]*([+-]?\d+\.?\d*)(kg|g|lb|oz)$/i);
if (customMatch) {
return {
value: parseFloat(customMatch[1]),
unit: customMatch[2].toLowerCase(),
stable: true
};
}
return null;
} catch (error) {
return null;
}
}
/**
* 解析二进制格式数据
*/
_parseBinary(buffer) {
for (const [name, protocol] of Object.entries(this.protocols)) {
if (name === 'ascii') continue;
try {
const result = protocol.parser(buffer);
if (result && typeof result.value === 'number' && !isNaN(result.value)) {
return result;
}
} catch (e) {
}
}
return this.protocols.A12.parser(buffer);
}
/**
* 自定义协议解析(主动读取模式使用)
*/
_parseCustom(buffer) {
const data = new Uint8Array(buffer);
const hexStr = this._bufferToHex(buffer);
const asciiStr = this._bufferToString(buffer);
const autoResult = this._autoParse(buffer);
if (autoResult && autoResult.value !== null && !isNaN(autoResult.value)) {
this._log(`[自定义协议] 自动解析成功: ${autoResult.value} ${autoResult.unit}`);
return {
...autoResult,
rawHex: hexStr,
rawAscii: asciiStr.trim()
};
}
this._log(`[自定义协议] 未识别的格式 - HEX: ${hexStr}, ASCII: ${asciiStr.trim()}`);
return {
value: null,
unit: 'kg',
stable: false,
rawHex: hexStr,
rawAscii: asciiStr.trim()
};
}
/**
* Buffer转16进制字符串
*/
_bufferToHex(buffer) {
const data = new Uint8Array(buffer);
return Array.from(data).map(b => b.toString(16).padStart(2, '0')).join(' ');
}
/**
* Buffer转字符串
*/
_bufferToString(buffer) {
const data = new Uint8Array(buffer);
return String.fromCharCode.apply(null, data);
}
/**
* 设置协议类型
*/
setProtocol(protocol) {
if (protocol === 'auto' || protocol === 'ascii' || this.protocols[protocol]) {
this.protocolType = protocol;
this._log(`协议已切换: ${protocol}`);
}
}
/**
* 获取当前配置
*/
getConfig() {
return {
deviceId: this.deviceId,
deviceName: this.deviceName,
serviceId: this.serviceId,
notifyCharId: this.notifyCharId,
writeCharId: this.writeCharId,
protocol: this.protocolType,
isConnected: this.isConnected
};
}
/**
* 从存储加载配置(优先从 Vuex,其次从 localStorage
*/
loadConfig() {
let scaleConfig = {};
if (store) {
try {
const state = store.state.scale;
if (state && state.scaleConfig) {
scaleConfig = state.scaleConfig;
this._log('配置从 Vuex 加载');
}
} catch (e) {
this._log(`Vuex 加载配置失败: ${e.message}`, 'error');
}
}
if (!scaleConfig.deviceId) {
const storageConfig = uni.getStorageSync('scaleConfig');
if (storageConfig) {
scaleConfig = storageConfig;
this._log('配置从 localStorage 加载');
}
}
if (scaleConfig) {
this.deviceId = scaleConfig.deviceId || null;
this.deviceName = scaleConfig.deviceName || '';
this.serviceId = scaleConfig.serviceId || null;
this.notifyCharId = scaleConfig.characteristicId || null;
this.writeCharId = scaleConfig.writeCharacteristicId || null;
}
return this.getConfig();
}
/**
* 自动连接已配置的设备
*/
async autoConnect() {
if (this.connecting || this.isConnected) {
return this.isConnected;
}
this.loadConfig();
// 调试日志:显示加载的配置
this._log(`配置信息: deviceId=${this.deviceId}, serviceId=${this.serviceId}, notifyCharId=${this.notifyCharId}, writeCharId=${this.writeCharId}`);
if (!this.deviceId || !this.serviceId) {
throw new Error('未配置称重设备,请先在设置中配置');
}
return this.connect();
}
/**
* 获取已连接的蓝牙设备列表
*/
_getConnectedDevices() {
return new Promise((resolve) => {
uni.getConnectedBluetoothDevices({
success: (res) => resolve(res.devices || []),
fail: () => resolve([])
});
});
}
/**
* 连接指定设备
*/
async connect() {
if (this.connecting || this.isConnected) {
return this.isConnected;
}
if (!this.deviceId) {
throw new Error('缺少设备ID');
}
this.connecting = true;
try {
await this.init();
// 检查设备是否已经连接(避免重复连接报错)
const connectedDevices = await this._getConnectedDevices();
const isAlreadyConnected = connectedDevices.some(d => d.deviceId === this.deviceId);
if (isAlreadyConnected) {
this._log('设备已连接,跳过创建连接');
} else {
this._log(`正在连接设备: ${this.deviceName || this.deviceId}`);
await new Promise((resolve, reject) => {
uni.createBLEConnection({
deviceId: this.deviceId,
timeout: 15000,
success: resolve,
fail: reject
});
});
}
try {
const mtuRes = await new Promise((res) => {
uni.setBLEMTU({
deviceId: this.deviceId,
mtu: 512,
success: res,
fail: () => res({ mtu: 23 })
});
});
this._mtu = (mtuRes.mtu || 512) - 3;
this._log(`MTU 协商: 请求 512, 实际=${mtuRes.mtu}, 写入块=${this._mtu}`);
} catch (e) {
this._log(`MTU 协商异常, 使用默认值 20: ${e.message}`, 'error');
this._mtu = 20;
}
await this._delay(800);
if (this.notifyCharId) {
try {
await new Promise((resolve, reject) => {
uni.notifyBLECharacteristicValueChange({
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.notifyCharId,
state: true,
success: resolve,
fail: reject
});
});
this._log(`通知已启用: ${this.notifyCharId}`);
} catch (e) {
this._log(`启用通知失败: ${e.message}`, 'error');
}
} else {
this._log(`未配置通知特征值,无法接收数据`, 'warn');
}
this.isConnected = true;
this.connecting = false;
this._log('连接成功');
this._notifyConnectionChange(true);
return true;
} catch (error) {
this._log(`连接失败: ${error.message}`, 'error');
this.isConnected = false;
this.connecting = false;
this._notifyError(error);
this._notifyConnectionChange(false);
throw error;
}
}
/**
* 断开连接
*/
async disconnect() {
if (this.pendingRead) {
clearTimeout(this.pendingRead.timer);
this.pendingRead.reject(new Error('连接已断开'));
this.pendingRead = null;
}
if (this.deviceId) {
try {
await new Promise((resolve, reject) => {
uni.closeBLEConnection({
deviceId: this.deviceId,
success: resolve,
fail: reject
});
});
} catch (e) {
this._log(`断开连接失败: ${e.message}`, 'error');
}
}
this.isConnected = false;
this._log('已断开连接');
this._notifyConnectionChange(false);
}
/**
* 发送命令
*/
async sendCommand(command) {
if (!this.isConnected) {
throw new Error('设备未连接');
}
if (!this.writeCharId) {
this._log(`未配置写入特征值,无法发送命令`, 'error');
throw new Error('未配置写入特征值');
}
let cmdData;
if (Array.isArray(command)) {
cmdData = new Uint8Array(command);
} else if (this.commands[command]) {
// 从命令表获取(如 'READ_WEIGHT' → [0x52]
cmdData = new Uint8Array(this.commands[command]);
} else {
throw new Error('未知的命令: ' + command);
}
// 显示 HEX 和 ASCII
const hexStr = Array.from(cmdData).map(b => b.toString(16).padStart(2, '0')).join(' ');
const asciiStr = String.fromCharCode(...cmdData).replace(/[^\x20-\x7E]/g, '?');
this._log(`发送命令: "${asciiStr}" (HEX: ${hexStr})`);
try {
await this._writeData(cmdData.buffer);
return true;
} catch (error) {
this._log(`命令发送失败: ${error.message}`, 'error');
throw error;
}
}
/**
* 写入数据到蓝牙设备(自动适配 MTU 和 writeType
*/
async _writeData(buffer, attempt = 0) {
const dataLen = buffer.byteLength;
const mtu = this._mtu || 20;
if (dataLen > mtu) {
this._log(`数据 ${dataLen}B > MTU ${mtu}B, 自动分片发送`);
return this._writeDataInChunks(buffer, mtu);
}
const writeTypes = [null, 'writeNoResponse', 'write'];
const maxAttempts = 3;
if (attempt >= maxAttempts) {
this._log(`写入重试 ${maxAttempts} 次仍失败,视为尾包/打印机忙导致`, 'error');
return { success: true, silent: true };
}
const writeType = writeTypes[attempt] || null;
const options = {
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.writeCharId,
value: buffer
};
if (writeType) {
options.writeType = writeType;
}
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
...options,
success: (res) => {
this._writeType = writeType;
this._log(`写入成功, writeType=${writeType || 'auto'}`);
resolve(res);
},
fail: (err) => {
this._log(`写入失败 attempt=${attempt + 1}, errCode=${err.errCode}, msg=${err.errMsg}`, 'error');
const delay = 100 + attempt * 150;
setTimeout(() => {
this._writeData(buffer, attempt + 1).then(resolve).catch(reject);
}, delay);
}
});
});
}
/**
* 分片发送数据
*/
async _writeDataInChunks(buffer, mtu = null) {
const totalLength = buffer.byteLength;
mtu = mtu || this._mtu || 20;
const dataView = new Uint8Array(buffer);
for (let offset = 0; offset < totalLength; offset += mtu) {
const end = Math.min(offset + mtu, totalLength);
const chunk = dataView.slice(offset, end);
const chunkBuffer = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength);
const isLastChunk = end >= totalLength;
try {
await this._writeChunk(chunkBuffer);
if (offset + mtu < totalLength) {
await this._delay(50);
}
} catch (err) {
if (isLastChunk) {
const retryDelays = [300, 600, 1000];
let delivered = false;
for (let attempt = 0; attempt < retryDelays.length; attempt++) {
const wait = retryDelays[attempt];
this._log(`尾包发送失败,第 ${attempt + 1}/${retryDelays.length} 次重试,等待 ${wait}ms...`, 'error');
await this._delay(wait);
try {
await this._writeChunkWithResponse(chunkBuffer);
this._log(`尾包重试成功`);
delivered = true;
break;
} catch (retryErr) {
this._log(`尾包第 ${attempt + 1} 次重试仍失败: ${retryErr.message}`, 'error');
}
}
if (delivered) {
continue;
}
this._log(`尾包 3 次重试均失败,数据可能不完整`, 'error');
return;
}
this._log(`分片发送失败 at offset ${offset}: ${err.message}`, 'error');
throw err;
}
}
}
/**
* 发送单个数据包(分片用)
*/
_writeChunk(chunkBuffer, retryWithWrite = false, attempt = 0) {
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.writeCharId,
value: chunkBuffer,
writeType: retryWithWrite ? 'write' : 'writeNoResponse',
success: resolve,
fail: async (err) => {
if (!retryWithWrite && err.errCode === 10007) {
this._log('writeNoResponse 不支持,降级为 write 模式', 'error');
try {
await this._delay(50);
await this._writeChunk(chunkBuffer, true, 0);
resolve();
} catch (downgradeErr) {
reject(downgradeErr);
}
return;
}
if (err.errCode === 10007 && attempt < 3) {
const wait = 100 * (attempt + 1);
this._log(`设备忙(10007)${wait}ms 后第 ${attempt + 1}/3 次重试...`, 'error');
try {
await this._delay(wait);
await this._writeChunk(chunkBuffer, retryWithWrite, attempt + 1);
resolve();
} catch (retryErr) {
reject(retryErr);
}
return;
}
reject(err);
}
});
});
}
/**
* 发送单个数据包 —— 强制使用 'write' 模式(带响应)
*/
_writeChunkWithResponse(chunkBuffer) {
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.writeCharId,
value: chunkBuffer,
writeType: 'write',
success: resolve,
fail: reject
});
});
}
/**
* 主动读取重量
*/
async readWeight(timeout) {
if (!this.isConnected) {
throw new Error('设备未连接');
}
if (this.pendingRead) {
throw new Error('已有读取请求等待中,请勿重复发送');
}
const actualTimeout = timeout || this.readTimeout;
return new Promise(async (resolve, reject) => {
const timer = setTimeout(() => {
this.pendingRead = null;
reject(new Error(`读取重量超时(${actualTimeout}ms),设备未返回数据`));
}, actualTimeout);
this.pendingRead = { resolve, reject, timer };
try {
await this.sendCommand('READ_WEIGHT');
this._log('已发送读取重量命令,等待设备响应...');
} catch (error) {
clearTimeout(timer);
this.pendingRead = null;
reject(error);
}
});
}
/**
* 去皮(归零)
*/
async tare() {
return this.sendCommand('TARE');
}
/**
* 砝码校准
* @param {number} weight 校准重量(默认1kg
*/
async calibrate(weight = 1) {
let command;
if (weight <= 1) {
command = 'CALIBRATE_1KG';
} else if (weight <= 2) {
command = 'CALIBRATE_2KG';
} else {
command = 'CALIBRATE_5KG';
}
return this.sendCommand(command);
}
/**
* 切换单位
* @param {string} unit kg, g, lb
*/
async changeUnit(unit) {
const unitMap = {
'kg': 'UNIT_KG',
'g': 'UNIT_G',
'lb': 'UNIT_LB'
};
const command = unitMap[unit];
if (!command) {
throw new Error('不支持的单位');
}
return this.sendCommand(command);
}
/**
* 获取当前重量
*/
getCurrentWeight() {
return { ...this.currentWeight };
}
/**
* 获取实时重量(带稳定性等待)
*/
async getStableWeight(timeout = 10000) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
const checkWeight = () => {
if (this.currentWeight.stable) {
resolve(this.currentWeight);
} else if (Date.now() - startTime > timeout) {
reject(new Error('等待重量稳定超时'));
} else {
setTimeout(checkWeight, 100);
}
};
checkWeight();
});
}
/**
* 发送自定义命令
*/
async sendRawCommand(commandArray) {
return this.sendCommand(commandArray);
}
/**
* 注册重量变化回调
*/
onWeightChange(callback) {
if (!this.weightChangeCallbacks.includes(callback)) {
this.weightChangeCallbacks.push(callback);
}
}
/**
* 取消重量变化回调
*/
offWeightChange(callback) {
const index = this.weightChangeCallbacks.indexOf(callback);
if (index > -1) {
this.weightChangeCallbacks.splice(index, 1);
}
}
/**
* 注册连接状态回调
*/
onConnectionChange(callback) {
if (!this.connectionChangeCallbacks.includes(callback)) {
this.connectionChangeCallbacks.push(callback);
}
}
/**
* 取消连接状态回调
*/
offConnectionChange(callback) {
const index = this.connectionChangeCallbacks.indexOf(callback);
if (index > -1) {
this.connectionChangeCallbacks.splice(index, 1);
}
}
/**
* 注册错误回调
*/
onError(callback) {
if (!this.errorCallbacks.includes(callback)) {
this.errorCallbacks.push(callback);
}
}
/**
* 取消错误回调
*/
offError(callback) {
const index = this.errorCallbacks.indexOf(callback);
if (index > -1) {
this.errorCallbacks.splice(index, 1);
}
}
/**
* 注册日志回调
*/
onLog(callback) {
if (!this.logCallbacks.includes(callback)) {
this.logCallbacks.push(callback);
}
}
/**
* 取消日志回调
*/
offLog(callback) {
const index = this.logCallbacks.indexOf(callback);
if (index > -1) {
this.logCallbacks.splice(index, 1);
}
}
/**
* 通知重量变化
*/
_notifyWeightChange(weight) {
this.weightChangeCallbacks.forEach(cb => {
try {
cb(weight);
} catch (e) {
this._log(`重量变化回调执行失败: ${e.message}`, 'error');
}
});
}
/**
* 通知连接状态变化
*/
_notifyConnectionChange(connected) {
this.connectionChangeCallbacks.forEach(cb => {
try {
cb(connected);
} catch (e) {
this._log(`连接状态回调执行失败: ${e.message}`, 'error');
}
});
}
/**
* 通知错误
*/
_notifyError(error) {
this.errorCallbacks.forEach(cb => {
try {
cb(error);
} catch (e) {
this._log(`错误回调执行失败: ${e.message}`, 'error');
}
});
}
/**
* 记录日志
*/
_log(message, level = 'info') {
console[level === 'error' ? 'error' : 'log'](`[称重服务] ${message}`);
this.logCallbacks.forEach(cb => {
try {
cb(message, level);
} catch (e) {
}
});
}
/**
* 延迟
*/
_delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
export default new BluetoothScaleService();