"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;