Files
huishou/dist/dev/mp-weixin/utils/bluetooth.js
T
2026-07-27 14:07:26 +08:00

452 lines
15 KiB
JavaScript

"use strict";
const common_vendor = require("../common/vendor.js");
let deviceId = "";
let serviceId = "";
let characteristicId = "";
let writeCharacteristicId = "";
let weightCallback = null;
const initBluetooth = () => {
return new Promise((resolve, reject) => {
common_vendor.index.openBluetoothAdapter({
success: () => {
console.log("Bluetooth adapter initialized");
resolve();
},
fail: (err) => {
console.error("Bluetooth init failed:", err);
reject(err);
}
});
});
};
const startDiscovery = (callback) => {
return new Promise((resolve) => {
const foundDevices = [];
common_vendor.index.startBluetoothDevicesDiscovery({
services: [],
success: () => {
console.log("Started discovery");
common_vendor.index.onBluetoothDeviceFound((res) => {
const devices = res.devices.filter((d) => d.name && (d.name.includes("Scale") || d.name.includes("秤") || d.name.includes("WXL")));
devices.forEach((device) => {
if (!foundDevices.find((d) => d.deviceId === device.deviceId)) {
foundDevices.push({
deviceId: device.deviceId,
name: device.name,
RSSI: device.RSSI || 0
});
}
});
callback([...foundDevices]);
});
setTimeout(() => {
common_vendor.index.stopBluetoothDevicesDiscovery();
resolve();
}, 1e4);
}
});
});
};
const stopDiscovery = () => {
common_vendor.index.stopBluetoothDevicesDiscovery();
};
const connectDevice = (deviceIdParam) => {
return new Promise((resolve, reject) => {
deviceId = deviceIdParam;
common_vendor.index.createBLEConnection({
deviceId: deviceIdParam,
success: () => {
console.log("Connected to device:", deviceIdParam);
getBLEDeviceServices(deviceIdParam).then(() => resolve()).catch(reject);
},
fail: (err) => {
console.error("Connection failed:", err);
reject(err);
}
});
});
};
const getBLEDeviceServices = (deviceIdParam) => {
return new Promise((resolve, reject) => {
common_vendor.index.getBLEDeviceServices({
deviceId: deviceIdParam,
success: (res) => {
console.log("=== Available Services ===");
res.services.forEach((s, i) => {
console.log(`${i + 1}. UUID: ${s.uuid}, Primary: ${s.isPrimary}`);
});
const commonServiceUUIDs = [
"0000FFE0",
"0000ffe0",
"0000180a",
"00001800",
"0000ffe5",
"0000fff0",
"fff0",
"0000ffe3",
"ffe3",
"0000ffe1",
"ffe1"
];
let foundService = res.services.find(
(s) => commonServiceUUIDs.some((uuid) => s.uuid.toLowerCase().startsWith(uuid.toLowerCase()))
);
if (!foundService && res.services.length > 0) {
foundService = res.services.find((s) => s.isPrimary) || res.services.find((s) => s.uuid.length === 4 || s.uuid.length === 8) || res.services[0];
console.log("Using fallback service:", foundService.uuid);
}
if (foundService) {
serviceId = foundService.uuid;
console.log("Selected service:", serviceId);
getBLEDeviceCharacteristics(deviceIdParam, foundService.uuid).then(() => resolve()).catch(reject);
} else {
reject(new Error("Service not found"));
}
},
fail: reject
});
});
};
const getBLEDeviceCharacteristics = (deviceIdParam, serviceIdParam) => {
return new Promise((resolve, reject) => {
common_vendor.index.getBLEDeviceCharacteristics({
deviceId: deviceIdParam,
serviceId: serviceIdParam,
success: (res) => {
console.log("=== Available Characteristics ===");
res.characteristics.forEach((c, i) => {
console.log(`${i + 1}. UUID: ${c.uuid}`);
console.log(` - Properties: notify=${c.properties.notify}, read=${c.properties.read}, write=${c.properties.write}, writeWithoutResponse=${c.properties.writeWithoutResponse}`);
});
const commonCharUUIDs = [
"0000FFE1",
"0000ffe1",
"0000ffe2",
"0000ffe4",
"0000fff1",
"fff1",
"0000ffe3",
"ffe3",
"00002a00",
"00002a01",
"00002a04",
"00002a05"
];
let characteristic = res.characteristics.find(
(c) => commonCharUUIDs.some((uuid) => c.uuid.toLowerCase().startsWith(uuid.toLowerCase())) && (c.properties.notify || c.properties.read)
);
const writeChar = res.characteristics.find((c) => c.properties.write || c.properties.writeWithoutResponse);
if (writeChar) {
writeCharacteristicId = writeChar.uuid;
console.log("Found write characteristic:", writeCharacteristicId);
}
if (!characteristic) {
characteristic = res.characteristics.find((c) => c.properties.notify);
}
if (!characteristic) {
characteristic = res.characteristics.find((c) => c.properties.read);
}
if (!characteristic && res.characteristics.length > 0) {
characteristic = res.characteristics.find((c) => c.uuid.length === 4 || c.uuid.length === 8) || res.characteristics[0];
console.log("Using fallback characteristic:", characteristic.uuid);
}
if (characteristic) {
characteristicId = characteristic.uuid;
console.log("Selected characteristic for read/notify:", characteristicId);
console.log("Properties:", characteristic.properties);
sendInitCommands(deviceIdParam, serviceIdParam);
if (characteristic.properties.notify) {
notifyBLECharacteristicValueChange(deviceIdParam, serviceIdParam, characteristic.uuid).then(() => resolve()).catch(reject);
} else if (characteristic.properties.read) {
readBLECharacteristicValue(deviceIdParam, serviceIdParam, characteristic.uuid).then(() => {
setupPeriodicRead(deviceIdParam, serviceIdParam, characteristic.uuid);
resolve();
}).catch(reject);
} else {
resolve();
}
} else {
reject(new Error("Characteristic not found"));
}
},
fail: reject
});
});
};
const sendInitCommands = (deviceIdParam, serviceIdParam) => {
if (!writeCharacteristicId) {
console.log("No write characteristic found, skipping init commands");
return;
}
const initCommands = [
stringToBuffer("START"),
stringToBuffer("ON"),
stringToBuffer("WEIGH"),
stringToBuffer("GET"),
stringToBuffer("1"),
new Uint8Array([1]).buffer,
new Uint8Array([2]).buffer,
new Uint8Array([3]).buffer,
new Uint8Array([85]).buffer,
new Uint8Array([170, 85]).buffer,
new Uint8Array([254, 1]).buffer,
new Uint8Array([255, 1]).buffer,
new Uint8Array([0, 0, 0, 1]).buffer
];
initCommands.forEach((cmd, index) => {
setTimeout(() => {
writeBLECharacteristicValue(deviceIdParam, serviceIdParam, writeCharacteristicId, cmd);
}, index * 200);
});
};
const writeBLECharacteristicValue = (deviceIdParam, serviceIdParam, characteristicIdParam, value) => {
common_vendor.index.writeBLECharacteristicValue({
deviceId: deviceIdParam,
serviceId: serviceIdParam,
characteristicId: characteristicIdParam,
value,
success: () => {
console.log("Wrote command:", arrayBufferToHex(value));
},
fail: (err) => {
console.log("Write failed (may be normal for read-only chars):", err.errMsg);
}
});
};
const stringToBuffer = (str) => {
const encoder = new TextEncoder();
return encoder.encode(str).buffer;
};
let readInterval = null;
const setupPeriodicRead = (deviceIdParam, serviceIdParam, characteristicIdParam) => {
if (readInterval) {
clearInterval(readInterval);
}
readInterval = setInterval(() => {
common_vendor.index.readBLECharacteristicValue({
deviceId: deviceIdParam,
serviceId: serviceIdParam,
characteristicId: characteristicIdParam,
success: (res) => {
handleBLEData(res.value);
},
fail: (err) => {
console.log("Periodic read failed:", err.errMsg);
}
});
}, 300);
console.log("Started periodic read every 300ms");
};
const notifyBLECharacteristicValueChange = (deviceIdParam, serviceIdParam, characteristicIdParam) => {
return new Promise((resolve, reject) => {
common_vendor.index.notifyBLECharacteristicValueChange({
deviceId: deviceIdParam,
serviceId: serviceIdParam,
characteristicId: characteristicIdParam,
state: true,
success: () => {
console.log("Notify enabled for:", characteristicIdParam);
resolve();
},
fail: (err) => {
console.log("Notify failed, trying read mode:", err.errMsg);
readBLECharacteristicValue(deviceIdParam, serviceIdParam, characteristicIdParam).then(() => {
setupPeriodicRead(deviceIdParam, serviceIdParam, characteristicIdParam);
resolve();
}).catch(reject);
}
});
});
};
const readBLECharacteristicValue = (deviceIdParam, serviceIdParam, characteristicIdParam) => {
return new Promise((resolve, reject) => {
common_vendor.index.readBLECharacteristicValue({
deviceId: deviceIdParam,
serviceId: serviceIdParam,
characteristicId: characteristicIdParam,
success: (res) => {
console.log("Read characteristic value received");
handleBLEData(res.value);
resolve();
},
fail: (err) => {
console.error("Read failed:", err);
reject(err);
}
});
});
};
const handleBLEData = (value) => {
console.log("=== BLE Data Received ===");
console.log("Data length:", value.byteLength, "bytes");
console.log("Hex:", arrayBufferToHex(value));
const asciiData = arrayBufferToString(value);
console.log("ASCII:", JSON.stringify(asciiData));
const weight = parseWeightData(value);
console.log("Parsed weight:", weight, "kg");
if (weight > 0 && weight < 1e3 && weightCallback) {
weightCallback(weight);
console.log("Weight sent to callback:", weight);
}
};
const listenWeightData = (callback) => {
weightCallback = callback;
console.log("Weight callback registered");
common_vendor.index.onBLECharacteristicValueChange((res) => {
console.log("BLE characteristic value changed event");
handleBLEData(res.value);
});
};
const parseWeightData = (buffer) => {
if (buffer.byteLength === 0) {
console.log("Empty buffer received");
return 0;
}
const dataView = new DataView(buffer);
const asciiData = arrayBufferToString(buffer);
const cleaned = asciiData.replace(/[\r\n\s\x00-\x1F]+/g, "");
console.log("Cleaned ASCII:", JSON.stringify(cleaned));
const textPatterns = [
/[+-]?\d+\.\d{1,3}\s*[kK][gG]?/,
/[+-]?\d+\.\d{1,3}/,
/[+-]?\d+\,\d{1,3}/,
/[+-]?\d+\.?\d*/
];
for (const pattern of textPatterns) {
const match = cleaned.match(pattern);
if (match) {
const numStr = match[0].replace(",", ".").replace(/[^0-9.-]/g, "");
const weight = parseFloat(numStr);
if (!isNaN(weight) && weight > 0 && weight < 1e3) {
console.log("Parsed from text:", weight);
return weight;
}
}
}
if (buffer.byteLength >= 2) {
const testValues = [];
if (buffer.byteLength >= 4) {
testValues.push(dataView.getInt32(0, true) / 1e3);
testValues.push(dataView.getInt32(0, false) / 1e3);
testValues.push(dataView.getFloat32(0, true));
testValues.push(dataView.getFloat32(0, false));
testValues.push(dataView.getUint32(0, true) / 1e3);
testValues.push(dataView.getUint32(0, false) / 1e3);
}
if (buffer.byteLength >= 2) {
testValues.push(dataView.getInt16(0, true) / 10);
testValues.push(dataView.getInt16(0, false) / 10);
testValues.push(dataView.getUint16(0, true) / 10);
testValues.push(dataView.getUint16(0, false) / 10);
testValues.push(dataView.getInt16(0, true) / 100);
testValues.push(dataView.getInt16(0, false) / 100);
testValues.push(dataView.getInt16(0, true) / 1e3);
testValues.push(dataView.getInt16(0, false) / 1e3);
}
if (buffer.byteLength >= 3) {
const byte1 = dataView.getUint8(0);
const byte2 = dataView.getUint8(1);
const byte3 = dataView.getUint8(2);
testValues.push((byte1 << 16 | byte2 << 8 | byte3) / 1e3);
testValues.push((byte3 << 16 | byte2 << 8 | byte1) / 1e3);
testValues.push((byte2 << 16 | byte1 << 8 | byte3) / 1e3);
}
console.log("Testing binary values:", testValues.filter((v) => v > 0 && v < 1e3));
for (const value of testValues) {
if (value > 0 && value < 1e3 && !isNaN(value)) {
console.log("Parsed from binary:", value);
return value;
}
}
}
if (buffer.byteLength >= 4) {
const bytes = new Uint8Array(buffer);
let bcdValue = "";
for (let i = 0; i < bytes.length; i++) {
const high = bytes[i] >> 4 & 15;
const low = bytes[i] & 15;
if (high >= 0 && high <= 9) bcdValue += high.toString();
if (low >= 0 && low <= 9) bcdValue += low.toString();
}
console.log("BCD value:", bcdValue);
if (bcdValue.length >= 1) {
let weight = parseFloat(bcdValue) / 1e3;
if (weight > 0 && weight < 1e3) {
console.log("Parsed from BCD:", weight);
return weight;
}
const dotIndex = bcdValue.length - 3;
if (dotIndex > 0) {
const formatted = bcdValue.slice(0, dotIndex) + "." + bcdValue.slice(dotIndex);
weight = parseFloat(formatted);
if (weight > 0 && weight < 1e3) {
console.log("Parsed from BCD with decimal:", weight);
return weight;
}
}
}
}
console.log("No valid weight found, returning 0");
return 0;
};
const arrayBufferToString = (buffer) => {
try {
const bytes = new Uint8Array(buffer);
let result = "";
for (let i = 0; i < bytes.length; i++) {
if (bytes[i] >= 32 && bytes[i] <= 126) {
result += String.fromCharCode(bytes[i]);
} else if (bytes[i] === 10 || bytes[i] === 13) {
result += bytes[i] === 10 ? "\n" : "\r";
}
}
return result;
} catch {
return "";
}
};
const arrayBufferToHex = (buffer) => {
const bytes = new Uint8Array(buffer);
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join(" ");
};
const disconnectDevice = () => {
return new Promise((resolve, reject) => {
if (readInterval) {
clearInterval(readInterval);
readInterval = null;
console.log("Stopped periodic read");
}
if (!deviceId) {
resolve();
return;
}
common_vendor.index.closeBLEConnection({
deviceId,
success: () => {
console.log("Disconnected successfully");
deviceId = "";
serviceId = "";
characteristicId = "";
writeCharacteristicId = "";
weightCallback = null;
resolve();
},
fail: (err) => {
console.log("Disconnect failed but clearing state:", err.errMsg);
deviceId = "";
serviceId = "";
characteristicId = "";
writeCharacteristicId = "";
weightCallback = null;
resolve();
}
});
});
};
exports.connectDevice = connectDevice;
exports.disconnectDevice = disconnectDevice;
exports.initBluetooth = initBluetooth;
exports.listenWeightData = listenWeightData;
exports.startDiscovery = startDiscovery;
exports.stopDiscovery = stopDiscovery;