refactor: update WeightScaleBridge integration and error handling
Some checks failed
CI / lint (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / build-library (pull_request) Has been cancelled
CI / build-android (pull_request) Has been cancelled
CI / build-ios (pull_request) Has been cancelled

This commit is contained in:
2025-12-16 16:39:05 +05:30
parent 88a89bcbd3
commit c14d41b345
4 changed files with 202 additions and 184 deletions

View File

@@ -1,6 +0,0 @@
// Inside the getPackages() method
override fun getPackages(): List<ReactPackage> {
val packages = PackageList(this).packages
packages.add(WeightScalePackage()) // Add this line
return packages
}

View File

@@ -1,10 +1,11 @@
package com.weightscalebridge package com.weightscalebridge
import com.facebook.react.bridge.ReactApplicationContext import android.Manifest
import com.facebook.react.module.annotations.ReactModule
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
import android.bluetooth.le.* import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
@@ -12,182 +13,181 @@ import android.os.CountDownTimer
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.facebook.react.bridge.* import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule import com.facebook.react.modules.core.DeviceEventManagerModule
import kotlin.math.abs import kotlin.math.pow
import kotlin.math.round
class WeightScaleModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { class WeightScaleModule(
private val reactContext: ReactApplicationContext
override fun getName(): String = "WeightScale" ) : ReactContextBaseJavaModule(reactContext) {
private var bluetoothAdapter: BluetoothAdapter? = null private var bluetoothAdapter: BluetoothAdapter? = null
private var scanner: BluetoothLeScanner? = null private var scanner: BluetoothLeScanner? = null
private var isScanning = false private var isScanning = false
private var timer: CountDownTimer? = null private var timer: CountDownTimer? = null
private var currentWeight = 0.0
private var candidateKg: Double = Double.NaN override fun getName(): String = "WeightScaleBridge"
private var stableSince: Long = 0L
private val STABLE_WINDOW_MS = 4000L init {
private val WEIGHT_TOL = 0.1 val manager =
private val MIN_VALID_KG = 5.0 reactContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val MAX_VALID_KG = 250.0 bluetoothAdapter = manager.adapter
private val MEASURE_WINDOW_MS = 20000L // 20 seconds timeout scanner = bluetoothAdapter?.bluetoothLeScanner
private val TARGET_MAC = "34:5C:F3:40:2C:D8" // Your scale MAC }
private fun sendEvent(eventName: String, params: WritableMap) { private fun sendEvent(name: String, params: WritableMap?) {
reactApplicationContext reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params) .emit(name, params)
} }
@ReactMethod @ReactMethod
fun startMeasuring() { fun isBluetoothEnabled(promise: Promise) {
if (isScanning) return
val bluetoothManager = reactApplicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
if (bluetoothAdapter == null || !bluetoothAdapter!!.isEnabled) {
val params = Arguments.createMap().apply {
putString("message", "Bluetooth is not enabled. Please turn it on in settings.")
}
sendEvent("onScaleError", params)
return
}
if (!hasRequiredPermissions()) {
val params = Arguments.createMap().apply {
putString("message", "Required permissions not granted.")
}
sendEvent("onScaleError", params)
return
}
scanner = bluetoothAdapter!!.bluetoothLeScanner
// Reset stability tracking
candidateKg = Double.NaN
stableSince = 0L
isScanning = true
// Send initial status
sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "scanning") })
// Use filter for your specific scale MAC (more efficient)
val scanFilter = ScanFilter.Builder()
.setDeviceAddress(TARGET_MAC)
.build()
val scanFilters = listOf(scanFilter)
val scanSettings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
try { try {
scanner!!.startScan(scanFilters, scanSettings, scanCallback) val enabled = bluetoothAdapter?.isEnabled ?: false
promise.resolve(enabled)
} catch (e: Exception) { } catch (e: Exception) {
val params = Arguments.createMap().apply { putString("message", e.message ?: "Scan start failed") } promise.reject("BLUETOOTH_CHECK_ERROR", e.message, e)
sendEvent("onScaleError", params) }
stopMeasuring() }
@ReactMethod
fun startScanning(promise: Promise) {
try {
if (!hasPermissions()) {
promise.reject("PERMISSION", "Bluetooth permissions not granted")
return return
} }
// 20-second timeout if (bluetoothAdapter?.isEnabled != true) {
timer = object : CountDownTimer(MEASURE_WINDOW_MS, 1000) { promise.reject("BLUETOOTH", "Bluetooth disabled")
override fun onTick(millisUntilFinished: Long) { return
// Optional: send remaining seconds if needed
} }
if (isScanning) {
promise.resolve(true)
return
}
isScanning = true
currentWeight = 0.0
scanner?.startScan(scanCallback)
sendEvent(
"onWeightScaleStatus",
Arguments.createMap().apply { putString("status", "scanning") }
)
timer = object : CountDownTimer(20000, 1000) {
override fun onTick(ms: Long) {}
override fun onFinish() { override fun onFinish() {
stopMeasuring() stopScanning(null)
sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "timeout") })
} }
}.start() }.start()
promise.resolve(true)
} catch (e: Exception) {
promise.reject("SCAN_ERROR", e.message, e)
}
} }
@ReactMethod @ReactMethod
fun stopMeasuring() { fun stopScanning(promise: Promise?) {
if (!isScanning) return
try { try {
scanner?.stopScan(scanCallback) scanner?.stopScan(scanCallback)
} catch (e: Exception) { } catch (_: Exception) {}
// Ignore
}
timer?.cancel() timer?.cancel()
timer = null
isScanning = false isScanning = false
sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "stopped") }) sendEvent(
"onWeightScaleStatus",
Arguments.createMap().apply { putString("status", "stopped") }
)
promise?.resolve(true)
} }
private val scanCallback = object : ScanCallback() { private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) { override fun onScanResult(type: Int, result: ScanResult) {
result ?: return val record = result.scanRecord ?: return
val scanRecord = result.scanRecord ?: return val data = record.manufacturerSpecificData
val manufacturerData = scanRecord.manufacturerSpecificData
if (manufacturerData != null && manufacturerData.size() > 0) { if (data.size() == 0) return
for (i in 0 until manufacturerData.size()) {
val data = manufacturerData.valueAt(i) ?: continue val raw = data.valueAt(0)
if (data.size >= 2) { if (raw.size < 2) return
val tenths = ((data[0].toInt() and 0xFF) shl 8) or (data[1].toInt() and 0xFF)
val kg = tenths / 10.0 val weight = (((raw[0].toInt() and 0xff) shl 8) or
if (kg in MIN_VALID_KG..MAX_VALID_KG) { (raw[1].toInt() and 0xff)) / 10.0
processWeight(kg)
} currentWeight = weight
}
sendEvent(
"onWeightData",
Arguments.createMap().apply {
putDouble("weight", weight)
putBoolean("isStable", true)
} }
)
sendEvent(
"onWeightScaleStatus",
Arguments.createMap().apply {
putString("status", "complete")
putString("message", "$weight kg")
} }
)
stopScanning(null)
} }
override fun onScanFailed(errorCode: Int) { override fun onScanFailed(errorCode: Int) {
val params = Arguments.createMap().apply { super.onScanFailed(errorCode)
sendEvent(
"onWeightScaleStatus",
Arguments.createMap().apply {
putString("status", "error")
putString("message", "Scan failed with code: $errorCode") putString("message", "Scan failed with code: $errorCode")
} }
sendEvent("onScaleError", params) )
stopMeasuring()
isScanning = false
} }
} }
private fun processWeight(kg: Double) { @ReactMethod
// Send live weight update fun calculateBMI(weight: Double, heightCm: Double, promise: Promise) {
val liveParams = Arguments.createMap().apply { putDouble("weight", kg) } try {
sendEvent("onLiveWeight", liveParams) if (heightCm <= 0) {
promise.reject("INVALID_HEIGHT", "Height must be greater than 0")
return
}
// Update status to measuring val bmi = weight / (heightCm / 100).pow(2)
sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "measuring") }) promise.resolve(round(bmi * 10) / 10)
} catch (e: Exception) {
val now = System.currentTimeMillis() promise.reject("BMI_ERROR", e.message, e)
// Stability detection
if (candidateKg.isNaN() || abs(kg - candidateKg) > WEIGHT_TOL) {
candidateKg = kg
stableSince = now
} else if (now - stableSince >= STABLE_WINDOW_MS) {
// Stable weight achieved
val stableParams = Arguments.createMap().apply { putDouble("weight", candidateKg) }
sendEvent("onStableWeight", stableParams)
sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "stable") })
stopMeasuring()
} }
} }
private fun hasRequiredPermissions(): Boolean { private fun hasPermissions(): Boolean {
val perms = mutableListOf<String>() val perms =
perms.add(android.Manifest.permission.ACCESS_FINE_LOCATION) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
arrayOf(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { Manifest.permission.BLUETOOTH_SCAN,
perms.add(android.Manifest.permission.BLUETOOTH_SCAN) Manifest.permission.BLUETOOTH_CONNECT
perms.add(android.Manifest.permission.BLUETOOTH_CONNECT) )
} else
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION
)
return perms.all { return perms.all {
ContextCompat.checkSelfPermission(reactApplicationContext, it) == PackageManager.PERMISSION_GRANTED ContextCompat.checkSelfPermission(reactContext, it) ==
PackageManager.PERMISSION_GRANTED
} }
} }
override fun onCatalystInstanceDestroy() {
super.onCatalystInstanceDestroy()
stopMeasuring() // Cleanup when RN instance destroys
}
} }

View File

@@ -1,16 +1,19 @@
package com.cureselect.weightpluse package com.weightscalebridge
import com.facebook.react.ReactPackage import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager import com.facebook.react.uimanager.ViewManager
class WeightScalePackage : ReactPackage { class WeightScaleBridgePackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
override fun createNativeModules(
reactContext: ReactApplicationContext
): List<NativeModule> {
return listOf(WeightScaleModule(reactContext)) return listOf(WeightScaleModule(reactContext))
} }
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> { override fun createViewManagers(
return emptyList() reactContext: ReactApplicationContext
} ): List<ViewManager<*, *>> = emptyList()
} }

View File

@@ -1,5 +1,26 @@
import WeightScaleBridge from './NativeWeightScaleBridge'; import { NativeModules, Platform } from 'react-native';
export function multiply(a: number, b: number): number { const LINKING_ERROR =
return WeightScaleBridge.multiply(a, b); `The package 'react-native-weight-scale-bridge' doesn't seem to be linked. Make sure:\n\n` +
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
'- You rebuilt the app after installing the package\n' +
'- You are not using Expo Go\n';
// Changed to match the native module name
const WeightScaleBridge = NativeModules.WeightScaleBridge;
if (!WeightScaleBridge) {
throw new Error(LINKING_ERROR);
} }
export const startScanning = () =>
WeightScaleBridge.startScanning();
export const stopScanning = () =>
WeightScaleBridge.stopScanning();
export const calculateBMI = (weight: number, heightCm: number) =>
WeightScaleBridge.calculateBMI(weight, heightCm);
export const isBluetoothEnabled = () =>
WeightScaleBridge.isBluetoothEnabled();