eagle-weighingScale #1

Merged
prathiyuman merged 2 commits from eagle-weighingScale into main 2025-12-16 16:41:11 +05:30
4 changed files with 212 additions and 41 deletions
Showing only changes of commit 88a89bcbd3 - Show all commits

View File

@@ -1,2 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
</manifest> </manifest>

View File

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

View File

@@ -2,22 +2,192 @@ package com.weightscalebridge
import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule import com.facebook.react.module.annotations.ReactModule
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.le.*
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.CountDownTimer
import androidx.core.content.ContextCompat
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
import kotlin.math.abs
@ReactModule(name = WeightScaleBridgeModule.NAME) class WeightScaleModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
class WeightScaleBridgeModule(reactContext: ReactApplicationContext) :
NativeWeightScaleBridgeSpec(reactContext) {
override fun getName(): String { override fun getName(): String = "WeightScale"
return NAME
}
// Example method private var bluetoothAdapter: BluetoothAdapter? = null
// See https://reactnative.dev/docs/native-modules-android private var scanner: BluetoothLeScanner? = null
override fun multiply(a: Double, b: Double): Double { private var isScanning = false
return a * b private var timer: CountDownTimer? = null
}
companion object { private var candidateKg: Double = Double.NaN
const val NAME = "WeightScaleBridge" private var stableSince: Long = 0L
}
} private val STABLE_WINDOW_MS = 4000L
private val WEIGHT_TOL = 0.1
private val MIN_VALID_KG = 5.0
private val MAX_VALID_KG = 250.0
private val MEASURE_WINDOW_MS = 20000L // 20 seconds timeout
private val TARGET_MAC = "34:5C:F3:40:2C:D8" // Your scale MAC
private fun sendEvent(eventName: String, params: WritableMap) {
reactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
@ReactMethod
fun startMeasuring() {
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 {
scanner!!.startScan(scanFilters, scanSettings, scanCallback)
} catch (e: Exception) {
val params = Arguments.createMap().apply { putString("message", e.message ?: "Scan start failed") }
sendEvent("onScaleError", params)
stopMeasuring()
return
}
// 20-second timeout
timer = object : CountDownTimer(MEASURE_WINDOW_MS, 1000) {
override fun onTick(millisUntilFinished: Long) {
// Optional: send remaining seconds if needed
}
override fun onFinish() {
stopMeasuring()
sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "timeout") })
}
}.start()
}
@ReactMethod
fun stopMeasuring() {
if (!isScanning) return
try {
scanner?.stopScan(scanCallback)
} catch (e: Exception) {
// Ignore
}
timer?.cancel()
timer = null
isScanning = false
sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "stopped") })
}
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
result ?: return
val scanRecord = result.scanRecord ?: return
val manufacturerData = scanRecord.manufacturerSpecificData
if (manufacturerData != null && manufacturerData.size() > 0) {
for (i in 0 until manufacturerData.size()) {
val data = manufacturerData.valueAt(i) ?: continue
if (data.size >= 2) {
val tenths = ((data[0].toInt() and 0xFF) shl 8) or (data[1].toInt() and 0xFF)
val kg = tenths / 10.0
if (kg in MIN_VALID_KG..MAX_VALID_KG) {
processWeight(kg)
}
}
}
}
}
override fun onScanFailed(errorCode: Int) {
val params = Arguments.createMap().apply {
putString("message", "Scan failed with code: $errorCode")
}
sendEvent("onScaleError", params)
stopMeasuring()
}
}
private fun processWeight(kg: Double) {
// Send live weight update
val liveParams = Arguments.createMap().apply { putDouble("weight", kg) }
sendEvent("onLiveWeight", liveParams)
// Update status to measuring
sendEvent("onStatusChange", Arguments.createMap().apply { putString("status", "measuring") })
val now = System.currentTimeMillis()
// 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 {
val perms = mutableListOf<String>()
perms.add(android.Manifest.permission.ACCESS_FINE_LOCATION)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
perms.add(android.Manifest.permission.BLUETOOTH_SCAN)
perms.add(android.Manifest.permission.BLUETOOTH_CONNECT)
}
return perms.all {
ContextCompat.checkSelfPermission(reactApplicationContext, it) == PackageManager.PERMISSION_GRANTED
}
}
override fun onCatalystInstanceDestroy() {
super.onCatalystInstanceDestroy()
stopMeasuring() // Cleanup when RN instance destroys
}
}

View File

@@ -1,33 +1,16 @@
package com.weightscalebridge package com.cureselect.weightpluse
import com.facebook.react.BaseReactPackage 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.module.model.ReactModuleInfo import com.facebook.react.uimanager.ViewManager
import com.facebook.react.module.model.ReactModuleInfoProvider
import java.util.HashMap
class WeightScaleBridgePackage : BaseReactPackage() { class WeightScalePackage : ReactPackage {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? { override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return if (name == WeightScaleBridgeModule.NAME) { return listOf(WeightScaleModule(reactContext))
WeightScaleBridgeModule(reactContext)
} else {
null
} }
}
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider { override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return ReactModuleInfoProvider { return emptyList()
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
moduleInfos[WeightScaleBridgeModule.NAME] = ReactModuleInfo(
WeightScaleBridgeModule.NAME,
WeightScaleBridgeModule.NAME,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCxxModule
true // isTurboModule
)
moduleInfos
} }
} }
}