package com.gizwits.reactnativegizwitssdkv5 import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Callback import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableArray import com.facebook.react.bridge.WritableMap import com.facebook.react.modules.core.DeviceEventManagerModule import com.gizwits.smart.sdk.GizDevice import com.gizwits.smart.sdk.GizSDKManager import com.gizwits.smart.sdk.bluetooth.bleCapability import com.gizwits.smart.sdk.bluetooth.startBleScan import com.gizwits.smart.sdk.bluetooth.stopBleScan import com.gizwits.smart.sdk.bluetooth.subscribeBluetoothDeviceList import com.gizwits.smart.sdk.common.GizConfiguration import com.gizwits.smart.sdk.exception.GizException import com.gizwits.smart.sdk.lan.lanCapability import com.gizwits.smart.sdk.lan.subscribeLanDeviceList import com.gizwits.smart.sdk.mqtt.mqttCapability import com.gizwits.smart.sdk.mqtt.queryBoundDevices import com.gizwits.smart.sdk.mqtt.subscribeBoundDeviceList import com.gizwits.smart.sdk.mqtt.subscribeBindEvent import com.google.gson.annotations.SerializedName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.last import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import org.json.JSONArray import org.json.JSONObject data class GizGetProductConfig( @SerializedName("productKey") val productKey: String, ) data class GizFeedback( @SerializedName("uploadLog") val uploadLog: Boolean, @SerializedName("content") val content: String, @SerializedName("contact") val contact: String, ) class RNGizSDKManagerModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private var mReactContext: ReactContext? = null private var isSubscriptDevices: List = listOf(); init { mReactContext = reactContext } companion object { internal var devices = listOf() } enum class EventName(val value: String) { DeviceDataListener("DeviceDataListener"), DeviceListListener("DeviceListListener"), DeviceStateListener("DeviceStateListener"), DeviceBindStateListener("DeviceBindStateListener"), } /** @hide */ @OptIn(ExperimentalCoroutinesApi::class) fun combineLatest( flow1: Flow, flow2: Flow, flow3: Flow, transform: suspend (a: T1, b: T2, c: T3) -> R ): Flow { return combine(flow1, flow2, flow3) { value1, value2, value3 -> Triple(value1, value2, value3) }.mapLatest { transform(it.first, it.second, it.third) } } /** @hide */ @OptIn(ExperimentalCoroutinesApi::class) fun combineLatest( flow1: Flow, flow2: Flow, transform: suspend (a: T1, b: T2) -> R ): Flow { return combine(flow1, flow2) { value1, value2 -> Pair(value1, value2) }.mapLatest { transform(it.first, it.second) } } override fun getName() = "RNGizSDKManagerModule" private val deviceListState: Flow> = combineLatest( GizSDKManager.subscribeBoundDeviceList(), GizSDKManager.subscribeBluetoothDeviceList(), GizSDKManager.subscribeLanDeviceList() ) { boundDeviceList, bluetoothDeviceList, lanDeviceList -> listOf(boundDeviceList, bluetoothDeviceList, lanDeviceList) .flatten() .distinct() } override fun initialize() { super.initialize() CoroutineScope(Dispatchers.IO).launch { GizSDKManager.subscribeBindEvent().collect { val data = JSONObject() data.put("did", it.first) data.put("isBind", it.second) sendEvent(EventName.DeviceBindStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data)) } } // 监听设备对象 deviceListState .debounce(500) .flatMapConcat { deviceList -> deviceList.map { dev -> if (!isSubscriptDevices.contains(dev.mac)) { CoroutineScope(Dispatchers.IO).launch { async { dev.bleCapability.subscribeModuleProfile().debounce(500).collect { if (it != null) { emitDeviceListChange() } } } async { combine( dev.bleCapability.subscribeStatus(), dev.bleCapability.subscribeIsLogin(), ) { status, isLogin -> Pair(status, isLogin) }.collect { val data = JSONObject() data.put("state", it.first.toInt()) data.put("isLogin", it.second) data.put("type", "BLE") data.put("device", dev.toJsonObject()) sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data)) } } async { dev.bleCapability.subscribeDp().collect{ val data = JSONObject() data.put("data", JSONObject(it.toString())) data.put("type", "BLE") data.put("device", dev.toJsonObject()) sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data)) } } async { dev.lanCapability.subscribeModuleProfile().debounce(500).collectLatest{ if (it != null) { emitDeviceListChange() } } } async { combineLatest( dev.lanCapability.subscribeStatus(), dev.lanCapability.subscribeIsLogin(), ) { status, isLogin -> Pair(status, isLogin) }.collectLatest { val data = JSONObject() data.put("state", it.first.toInt()) data.put("isLogin", it.second) data.put("type", "LAN") data.put("device", dev.toJsonObject()) sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data)) } } async { dev.lanCapability.subscribeDp().collect{ val data = JSONObject() data.put("data", JSONObject(it.toString())) data.put("type", "LAN") data.put("device", dev.toJsonObject()) sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data)) } } async { combineLatest( dev.mqttCapability.subscribeStatus(), dev.mqttCapability.subscribeIsLogin(), ) { status, isLogin -> Pair(status, isLogin) }.collectLatest { val data = JSONObject() data.put("state", it.first.toInt()) data.put("isLogin", it.second) data.put("type", "MQTT") data.put("device", dev.toJsonObject()) sendEvent(EventName.DeviceStateListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data)) } } // 不会主动发生改表 // async { // dev.mqttCapability.subscribeModuleProfile().debounce(500).collectLatest{ // if (it != null) { // emitDeviceListChange() // } // } // } async { dev.mqttCapability.subscribeDp().collect{ val data = JSONObject() data.put("data", JSONObject(it.toString())) data.put("type", "MQTT") data.put("device", dev.toJsonObject()) sendEvent(EventName.DeviceDataListener.name, GizRNCallbackManager.jsonObject2WriteableMap(data)) } } } } } /* 更新当前已经订阅的设备 如果设备消失,又出现,就可以正常的重新订阅 */ isSubscriptDevices = deviceList.map{ it.mac } emptyFlow() } .launchIn(CoroutineScope(Dispatchers.IO)) // 推送设备列表变化 CoroutineScope(Dispatchers.IO).launch { deviceListState.debounce(500).collectLatest { deviceList -> devices = deviceList emitDeviceListChange() } } } /** * RN的场景下 * module profile 变更 相当于设备列表变更 */ suspend fun emitDeviceListChange() { val deviceList = deviceListState.first() // 缓存变更 val deviceListJson = JSONArray() deviceList.forEach{ item -> deviceListJson.put(item.toJsonObject()) } sendEvent(EventName.DeviceListListener.name, GizRNCallbackManager.jsonArray2WriteableArray(deviceListJson)) } fun sendEvent(name:String, data: WritableArray?) { mReactContext!! .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(name, data) } fun sendEvent(name:String, data: WritableMap?) { mReactContext!! .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(name, data) } @ReactMethod fun initSDK(options: ReadableMap, result: Callback) { var config = RNGizParamsChecker.check(options, result, GizConfiguration::class.java) config?.let { CoroutineScope(Dispatchers.Main).launch { try { GizSDKManager.setDebug(true) GizSDKManager.initSDK(config) GizRNCallbackManager.callbackWithResult(callback = result, result = Result.success(0)) } catch (e: GizException) { GizRNCallbackManager.callbackWithResult(callback = result, result = Result.failure(e)) } } } } @ReactMethod fun getProductDataPointConfig(options: ReadableMap, result: Callback) { var config = RNGizParamsChecker.check(options, result, GizGetProductConfig::class.java) config?.let { CoroutineScope(Dispatchers.IO).launch { try { val config = GizSDKManager.getProductDataPointConfig(config.productKey) if (config != null) { GizRNCallbackManager.callbackWithResult(callback = result, result = Result.success(config)) } else { GizRNCallbackManager.callbackWithResult(callback = result, result = Result.failure(GizException.GIZ_SITE_PRODUCTKEY_INVALID)) } } catch (e: GizException) { GizRNCallbackManager.callbackWithResult(callback = result, result = Result.failure(e)) } } } } @ReactMethod fun getDevices(options: ReadableMap, result: Callback) { CoroutineScope(Dispatchers.IO).launch { // 先查询绑定设备,让 deviceListState 的 Flow 能收到最新数据 val _ignore = try { GizSDKManager.queryBoundDevices() } catch (e: Exception) { val writableMap = Arguments.createMap() writableMap.putArray("data", Arguments.createArray()) writableMap.putBoolean("success", false) writableMap.putInt("error", -1) writableMap.putString("message", e.message ?: "query failed") result.invoke(null, writableMap) return@launch } // 等待 deviceListState 合并后的最终结果(bound + bluetooth + lan) val deviceList = try { deviceListState.first() } catch (e: Exception) { val writableMap = Arguments.createMap() writableMap.putArray("data", Arguments.createArray()) writableMap.putBoolean("success", false) writableMap.putInt("error", -1) writableMap.putString("message", e.message ?: "device list state error") result.invoke(null, writableMap) return@launch } val jsonData = JSONArray() deviceList.forEach { device -> jsonData.put(device.toJsonObject()) } val writableMap = Arguments.createMap() writableMap.putArray("data", GizRNCallbackManager.jsonArray2WriteableArray(jsonData)) writableMap.putBoolean("success", true) writableMap.putInt("error", 0) writableMap.putString("message", "") result.invoke(null, writableMap) } } @ReactMethod fun startBleScan(options: ReadableMap, result: Callback) { GizSDKManager.startBleScan() GizRNCallbackManager.callbackWithResult(callback = result, result = Result.success(0)) } @ReactMethod fun stopBleScan(options: ReadableMap, result: Callback) { GizSDKManager.stopBleScan() GizRNCallbackManager.callbackWithResult(callback = result, result = Result.success(0)) } @ReactMethod fun cleanCache(options: ReadableMap, result: Callback) { CoroutineScope(Dispatchers.IO).launch { GizSDKManager.cleanCache() GizRNCallbackManager.callbackWithResult(callback = result, result = Result.success(0)) } } @ReactMethod fun feedback(options: ReadableMap, result: Callback) { var config = RNGizParamsChecker.check(options, result, GizFeedback::class.java) config?.let { CoroutineScope(Dispatchers.IO).launch { GizSDKManager.feedback(config.uploadLog,config.contact,config.content) GizRNCallbackManager.callbackWithResult(callback = result, result = Result.success(0)) } } } }