From f8730c100627bf7a08c2eb4947b988f9970006d5 Mon Sep 17 00:00:00 2001 From: Khushbu Shah Date: Wed, 22 May 2024 10:11:07 +0530 Subject: [PATCH 1/2] Bugfix: Fixed crash while app tries to get matter device param value. --- .../espressif/local_control/mDNSManager.java | 2 +- .../com/espressif/matter/ChipClientHelper.kt | 101 ++++++++++-------- 2 files changed, 57 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/espressif/local_control/mDNSManager.java b/app/src/main/java/com/espressif/local_control/mDNSManager.java index 21218ae..998e21e 100644 --- a/app/src/main/java/com/espressif/local_control/mDNSManager.java +++ b/app/src/main/java/com/espressif/local_control/mDNSManager.java @@ -127,7 +127,7 @@ public void onServiceFound(NsdServiceInfo serviceInfo) { // If the resolver is free, resolve the service to get all the details if (resolveListenerBusy.compareAndSet(false, true)) { - if (mNsdManager != null) { + if (resolveListener != null) { mNsdManager.resolveService(serviceInfo, resolveListener); } diff --git a/app/src/main/java/com/espressif/matter/ChipClientHelper.kt b/app/src/main/java/com/espressif/matter/ChipClientHelper.kt index 1f168b9..7f91057 100644 --- a/app/src/main/java/com/espressif/matter/ChipClientHelper.kt +++ b/app/src/main/java/com/espressif/matter/ChipClientHelper.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.future.future import org.greenrobot.eventbus.EventBus import java.math.BigInteger +import java.util.concurrent.ExecutionException class ChipClientHelper constructor(private val espApp: EspApplication) { @@ -48,63 +49,73 @@ class ChipClientHelper constructor(private val espApp: EspApplication) { return; } - for ((_, g) in espApp.groupMap.entries) { - if (g.isMatter) { - val nodeDetails = g.nodeDetails - if (nodeDetails != null) { - for ((nodeId, mNodeId) in nodeDetails.entries) { - var fabricId = "" - var ipk = "" - var rootCa = "" - var groupCatIdOperate = "" - if (matterNodeId != mNodeId) { - continue - } - Log.d( - TAG, - "Node detail, node id : $nodeId and matter node id : $matterNodeId" - ) - if (g.fabricDetails != null) { - fabricId = g.fabricDetails.fabricId - rootCa = g.fabricDetails.rootCa - ipk = g.fabricDetails.ipk - groupCatIdOperate = g.fabricDetails.groupCatIdOperate - if (!espApp.chipClientMap.containsKey(matterNodeId)) { - if (!TextUtils.isEmpty(fabricId) && !TextUtils.isEmpty(rootCa) - && !TextUtils.isEmpty(ipk) && !TextUtils.isEmpty(matterNodeId) - && !TextUtils.isEmpty(matterNodeId) - ) { - val chipClient = ChipClient( - espApp, g.groupId, fabricId, rootCa, ipk, groupCatIdOperate - ) - espApp.chipClientMap.put(matterNodeId, chipClient) - } + try { + + for ((_, g) in espApp.groupMap.entries) { + if (g.isMatter) { + val nodeDetails = g.nodeDetails + if (nodeDetails != null) { + for ((nodeId, mNodeId) in nodeDetails.entries) { + var fabricId = "" + var ipk = "" + var rootCa = "" + var groupCatIdOperate = "" + if (matterNodeId != mNodeId) { + continue } - espApp.fetchDeviceMatterInfo(matterNodeId, nodeId) - val node: EspNode? = espApp.nodeMap.get(nodeId) - if (node != null) { - getCurrentValues(nodeId, matterNodeId, node) -// val nodeType = node.newNodeType -// if (!TextUtils.isEmpty(nodeType) && nodeType == AppConstants.NODE_TYPE_PURE_MATTER) { -// espApp.addParamsForMatterOnlyDevice(nodeId, matterNodeId, node) -// } + Log.d( + TAG, + "Node detail, node id : $nodeId and matter node id : $matterNodeId" + ) + if (g.fabricDetails != null) { + fabricId = g.fabricDetails.fabricId + rootCa = g.fabricDetails.rootCa + ipk = g.fabricDetails.ipk + groupCatIdOperate = g.fabricDetails.groupCatIdOperate + if (!espApp.chipClientMap.containsKey(matterNodeId)) { + if (!TextUtils.isEmpty(fabricId) && !TextUtils.isEmpty(rootCa) + && !TextUtils.isEmpty(ipk) && !TextUtils.isEmpty( + matterNodeId + ) + && !TextUtils.isEmpty(matterNodeId) + ) { + val chipClient = ChipClient( + espApp, + g.groupId, + fabricId, + rootCa, + ipk, + groupCatIdOperate + ) + espApp.chipClientMap.put(matterNodeId, chipClient) + } + } + espApp.fetchDeviceMatterInfo(matterNodeId, nodeId) + val node: EspNode? = espApp.nodeMap.get(nodeId) + if (node != null) { + getCurrentValues(nodeId, matterNodeId, node) + } + Log.d(TAG, "Init and fetch cluster info done for the device") } - Log.d(TAG, "Init and fetch cluster info done for the device") } } } } + } catch (e: Exception) { + e.printStackTrace() + } finally { + var updateEvent = UpdateEvent(UpdateEventType.EVENT_MATTER_DEVICE_CONNECTIVITY) + var data = Bundle() + data.putString(AppConstants.KEY_MATTER_NODE_ID, matterNodeId) + updateEvent.data = data + EventBus.getDefault().post(updateEvent) } - var updateEvent = UpdateEvent(UpdateEventType.EVENT_MATTER_DEVICE_CONNECTIVITY) - var data = Bundle() - data.putString(AppConstants.KEY_MATTER_NODE_ID, matterNodeId) - updateEvent.data = data - EventBus.getDefault().post(updateEvent) } fun initChipClientInBackground(matterNodeId: String) = GlobalScope.future { initChipClient(matterNodeId) } + @Throws(ExecutionException::class) fun getCurrentValues(nodeId: String?, matterNodeId: String?, node: EspNode) { val id = BigInteger(matterNodeId, 16) From 0a06cf684dbef15caa6110a3e7f6e6014ddb9c3b Mon Sep 17 00:00:00 2001 From: Khushbu Shah Date: Wed, 22 May 2024 10:11:40 +0530 Subject: [PATCH 2/2] Matter: Added support for controlling matter-only devices remotely via controller. Commands are sent to controller remotely and controller relays the commands to connected matter-only devices. --- app/build.gradle | 4 +- .../main/java/com/espressif/AppConstants.kt | 12 + .../com/espressif/ESPControllerAPIKeys.kt | 61 ++ .../java/com/espressif/EspApplication.java | 59 +- .../java/com/espressif/JsonDataParser.java | 141 ++++ .../com/espressif/cloudapi/ApiManager.java | 139 ++-- .../matter/RemoteControlApiHelper.java | 604 ++++++++++++++++++ .../ui/activities/EspDeviceActivity.java | 134 ++-- .../ui/adapters/EspDeviceAdapter.java | 185 ++++-- .../espressif/ui/adapters/ParamAdapter.java | 325 +++++++--- .../java/com/espressif/ui/models/EspNode.java | 28 + .../java/com/espressif/utils/NodeUtils.kt | 35 + app/src/main/res/values/strings.xml | 1 + 13 files changed, 1487 insertions(+), 241 deletions(-) create mode 100644 app/src/main/java/com/espressif/ESPControllerAPIKeys.kt create mode 100644 app/src/main/java/com/espressif/matter/RemoteControlApiHelper.java create mode 100644 app/src/main/java/com/espressif/utils/NodeUtils.kt diff --git a/app/build.gradle b/app/build.gradle index 334a921..4e7982a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,8 +55,8 @@ android { applicationId "com.espressif.rainmaker" minSdkVersion 27 targetSdkVersion 34 - versionCode 117 - versionName "3.2.4 - ${getGitHash()}" + versionCode 119 + versionName "3.3.0 - ${getGitHash()}" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField "String", "GitHash", "\"${getGitHash()}\"" diff --git a/app/src/main/java/com/espressif/AppConstants.kt b/app/src/main/java/com/espressif/AppConstants.kt index d7da096..7636698 100644 --- a/app/src/main/java/com/espressif/AppConstants.kt +++ b/app/src/main/java/com/espressif/AppConstants.kt @@ -73,6 +73,7 @@ class AppConstants { const val SERVICE_TYPE_TIME = "esp.service.time" const val SERVICE_TYPE_LOCAL_CONTROL = "esp.service.local_control" const val SERVICE_TYPE_SYSTEM = "esp.service.system" + const val SERVICE_TYPE_MATTER_CONTROLLER = "esp.service.matter-controller" // Param Types const val PARAM_TYPE_NAME = "esp.param.name" @@ -89,6 +90,9 @@ class AppConstants { const val PARAM_TYPE_REBOOT = "esp.param.reboot" const val PARAM_TYPE_FACTORY_RESET = "esp.param.factory-reset" const val PARAM_TYPE_WIFI_RESET = "esp.param.wifi-reset" + const val PARAM_TYPE_MATTER_DEVICES = "esp.param.matter-devices" + const val PARAM_TYPE_MATTER_CTRL_DATA_VERSION = + "esp.param.matter-controller-data-version" // Param names const val PARAM_POWER = "Power" @@ -345,6 +349,8 @@ class AppConstants { const val KEY_AUTOMATION = "automation" const val KEY_LOAD_AUTOMATION_PAGE = "load_automation" const val KEY_SYSTEM = "System" + const val KEY_MATTER_CONTROLLER = "Matter-Controller" + const val KEY_REACHABLE = "reachable" const val KEY_OPERATION = "operation" const val KEY_OPERATION_ADD = "add" @@ -461,6 +467,12 @@ class AppConstants { const val PRIVILEGE_ADMIN = 5 const val PRIVILEGE_OPERATE = 3 + const val NODE_STATUS_OFFLINE = 1; + const val NODE_STATUS_ONLINE = 2; + const val NODE_STATUS_LOCAL = 3; + const val NODE_STATUS_MATTER_LOCAL = 4; + const val NODE_STATUS_REMOTELY_CONTROLLABLE = 5; + enum class UpdateEventType { EVENT_DEVICE_ADDED, EVENT_DEVICE_REMOVED, diff --git a/app/src/main/java/com/espressif/ESPControllerAPIKeys.kt b/app/src/main/java/com/espressif/ESPControllerAPIKeys.kt new file mode 100644 index 0000000..af1ba75 --- /dev/null +++ b/app/src/main/java/com/espressif/ESPControllerAPIKeys.kt @@ -0,0 +1,61 @@ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.espressif + +class ESPControllerAPIKeys { + + companion object { + + const val KEY_MATTER_CONTROLLER = "matter-controller" + const val KEY_MATTER_CONTROLLER_DATA_VERSION = "matter-controller-data-version" + const val KEY_MATTER_CONTROLLER_DATA = "matter-controller-data" + const val KEY_DATA = "data" + const val KEY_ENABLED = "enabled" + const val KEY_REACHABLE = "reachable" + const val KEY_MATTER_NODES = "matter-nodes" + const val KEY_MATTER_NODE_ID = "matter-node-id" + const val KEY_ENDPOINTS = "endpoints" + const val KEY_ENDPOINT_ID = "endpoint-id" + const val KEY_CLUSTERS = "clusters" + const val KEY_CLUSTER_ID = "cluster-id" + const val KEY_COMMANDS = "commands" + const val KEY_COMMAND_ID = "command-id" + + const val ENDPOINT_ID_1 = "0x1" + + const val CLUSTER_ID_ON_OFF = "0x6" + const val CLUSTER_ID_LEVEL_CONTROL = "0x8" + const val CLUSTER_ID_COLOR_CONTROL = "0x300" + const val CLUSTER_ID_THERMOSTAT = "0x201" + const val CLUSTER_ID_TEMPERATURE_MEASUREMENT = "0x402" + + const val COMMAND_ID_OFF = "0x0" + const val COMMAND_ID_ON = "0x1" + const val COMMAND_ID_TOGGLE = "0x2" + const val COMMAND_ID_MOVE_TO_LEVEL_WITH_ON_OFF = "0x0" + const val COMMAND_ID_MOVE_TO_SATURATION = "0x3" + const val COMMAND_ID_MOVE_TO_HUE = "0x0" + + const val ATTRIBUTE_ID_ON_OFF = "0x0" + const val ATTRIBUTE_ID_BRIGHTNESS_LEVEL = "0x0" + const val ATTRIBUTE_ID_CURRENT_HUE = "0x0" + const val ATTRIBUTE_ID_CURRENT_SATURATION = "0x1" + const val ATTRIBUTE_ID_LOCAL_TEMPERATURE = "0x0" + const val ATTRIBUTE_ID_SYSTEM_MODE = "0x1c" + const val ATTRIBUTE_ID_OCCUPIED_COOLING_SETPOINT = "0x11" + const val ATTRIBUTE_ID_OCCUPIED_HEATING_SETPOINT = "0x12" + const val ATTRIBUTE_ID_MEASURED_TEMPERATURE = "0x0" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/espressif/EspApplication.java b/app/src/main/java/com/espressif/EspApplication.java index e276777..04d33db 100644 --- a/app/src/main/java/com/espressif/EspApplication.java +++ b/app/src/main/java/com/espressif/EspApplication.java @@ -83,6 +83,7 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.Iterator; @@ -116,6 +117,7 @@ public class EspApplication extends Application { public HashMap chipClientMap; public HashMap> matterDeviceInfoMap; public ArrayList availableMatterDevices; + public HashMap> controllerDevices; public EspOtaUpdate otaUpdateInfo; private SharedPreferences appPreferences; @@ -149,6 +151,7 @@ public void onCreate() { chipClientMap = new HashMap<>(); matterDeviceInfoMap = new HashMap<>(); availableMatterDevices = new ArrayList<>(); + controllerDevices = new HashMap<>(); appPreferences = getSharedPreferences(AppConstants.ESP_PREFERENCES, Context.MODE_PRIVATE); BASE_URL = appPreferences.getString(AppConstants.KEY_BASE_URL, BuildConfig.BASE_URL); @@ -215,7 +218,11 @@ public void changeAppState(AppState newState, Bundle extras) { if (!chipClientMap.containsKey(matterNodeId)) { clientHelper.initChipClientInBackground(matterNodeId); } else { - clientHelper.getCurrentValues(nodeId, matterNodeId, nodeMap.get(nodeId)); + try { + clientHelper.getCurrentValues(nodeId, matterNodeId, nodeMap.get(nodeId)); + } catch (ExecutionException e) { + e.printStackTrace(); + } } } break; @@ -529,6 +536,7 @@ public void onSuccess(Bundle data) { } } } + setRemoteDeviceStatus(); changeAppState(AppState.GET_DATA_SUCCESS, null); } @@ -544,6 +552,49 @@ public void onNetworkFailure(Exception exception) { }); } + private void setRemoteDeviceStatus() { + + for (Map.Entry> entry : controllerDevices.entrySet()) { + + String controllerNodeId = entry.getKey(); + HashMap matterOnlyDevices = entry.getValue(); + + for (Map.Entry controllerDevice : matterOnlyDevices.entrySet()) { + String matterDeviceId = controllerDevice.getKey(); + String jsonStr = controllerDevice.getValue(); + + if (jsonStr != null) { + try { + JSONObject deviceJson = new JSONObject(jsonStr); + boolean enabled = deviceJson.optBoolean(AppConstants.KEY_ENABLED); + boolean reachable = deviceJson.optBoolean(AppConstants.KEY_REACHABLE); + + if (enabled && reachable) { + + if (matterRmNodeIdMap.containsValue(matterDeviceId)) { + for (Map.Entry matterDevice : matterRmNodeIdMap.entrySet()) { + if (matterDeviceId.equals(matterDevice.getValue())) { + String rmNodeId = matterDevice.getKey(); + if (nodeMap.containsKey(rmNodeId)) { + int nodeStatus = nodeMap.get(rmNodeId).getNodeStatus(); + if (nodeStatus != AppConstants.NODE_STATUS_MATTER_LOCAL && nodeStatus != AppConstants.NODE_STATUS_LOCAL) { + Log.d(TAG, "Set Node status to remotely controllable for node id : " + rmNodeId); + nodeMap.get(rmNodeId).setNodeStatus(AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE); + } + } + } + } + } + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + EventBus.getDefault().post(new UpdateEvent(UpdateEventType.EVENT_DEVICE_STATUS_UPDATE)); + } + } + private void initChipControllerForHomeGroup() { Log.d(TAG, "============================= init ChipController for home group"); @@ -619,12 +670,17 @@ public void fetchDeviceMatterInfo(String matterNodeId, String nodeId) { } matterDeviceInfoMap.put(matterNodeId, matterDeviceInfo); nodeMap.get(nodeId).setOnline(true); + nodeMap.get(nodeId).setNodeStatus(AppConstants.NODE_STATUS_MATTER_LOCAL); availableMatterDevices.add(matterNodeId); } else { matterDeviceInfoMap.remove(matterNodeId); availableMatterDevices.remove(matterNodeId); chipClientMap.remove(matterNodeId); nodeMap.get(nodeId).setOnline(false); + if (!Arrays.asList(AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE, AppConstants.NODE_STATUS_LOCAL, + AppConstants.NODE_STATUS_ONLINE).contains(nodeMap.get(nodeId).getNodeStatus())) { + nodeMap.get(nodeId).setNodeStatus(AppConstants.NODE_STATUS_OFFLINE); + } } } catch (ExecutionException e) { e.printStackTrace(); @@ -1088,6 +1144,7 @@ public void onSuccess(Bundle data) { localNode.setIpAddress(localDevice.getIpAddr()); localNode.setPort(localDevice.getPort()); localNode.setOnline(true); + localNode.setNodeStatus(AppConstants.NODE_STATUS_LOCAL); localDeviceMap.put(localNode.getNodeId(), localDevice); } diff --git a/app/src/main/java/com/espressif/JsonDataParser.java b/app/src/main/java/com/espressif/JsonDataParser.java index ddf3339..8651e9f 100644 --- a/app/src/main/java/com/espressif/JsonDataParser.java +++ b/app/src/main/java/com/espressif/JsonDataParser.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; public class JsonDataParser { @@ -379,6 +380,7 @@ public static void setAllParams(EspApplication espAppContext, EspNode node, JSON JSONObject timeJson = paramsJson.optJSONObject(AppConstants.KEY_TIME); JSONObject localControlJson = paramsJson.optJSONObject(AppConstants.KEY_LOCAL_CONTROL); JSONObject systemServiceJson = paramsJson.optJSONObject(AppConstants.KEY_SYSTEM); + JSONObject controllerServiceJson = paramsJson.optJSONObject(AppConstants.KEY_MATTER_CONTROLLER); int scheduleCnt = 0, sceneCnt = 0; if (devices != null) { @@ -781,6 +783,145 @@ public static void setAllParams(EspApplication espAppContext, EspNode node, JSON } } } + } else if (AppConstants.SERVICE_TYPE_MATTER_CONTROLLER.equals(service.getType()) && controllerServiceJson != null) { + + // Matter controller service + ArrayList controllerParams = service.getParams(); + if (controllerParams != null) { + + for (Param controllerParam : controllerParams) { + + String type = controllerParam.getParamType(); + + if (!TextUtils.isEmpty(type) && AppConstants.PARAM_TYPE_MATTER_CTRL_DATA_VERSION.equals(type)) { + + controllerParam.setLabelValue(controllerServiceJson.optString(controllerParam.getName())); + + } else if (!TextUtils.isEmpty(type) && AppConstants.PARAM_TYPE_MATTER_DEVICES.equals(type)) { + + JSONObject matterDevicesJson = controllerServiceJson.optJSONObject(controllerParam.getName()); + Iterator keys = matterDevicesJson.keys(); + HashMap matterDevices = new HashMap<>(); + + while (keys.hasNext()) { + String matterDeviceId = keys.next(); + JSONObject matterDeviceJson = matterDevicesJson.optJSONObject(matterDeviceId); + + if (matterDeviceId != null && matterDeviceJson != null) { + String value = matterDeviceJson.toString(); + matterDevices.put(matterDeviceId, value); + } + } + setRemoteDeviceParamValues(espAppContext, nodeId); + } + } + } + } + } + } + } + + private static void setRemoteDeviceParamValues(EspApplication espApp, String controllerNodeId) { + + if (espApp.controllerDevices.containsKey(controllerNodeId)) { + + HashMap matterOnlyDevices = espApp.controllerDevices.get(controllerNodeId); + + for (Map.Entry controllerDevice : matterOnlyDevices.entrySet()) { + String matterDeviceId = controllerDevice.getKey(); + String jsonStr = controllerDevice.getValue(); + + if (jsonStr != null) { + try { + JSONObject deviceJson = new JSONObject(jsonStr); + boolean enabled = deviceJson.optBoolean(AppConstants.KEY_ENABLED); + boolean reachable = deviceJson.optBoolean(AppConstants.KEY_REACHABLE); + + if (espApp.matterRmNodeIdMap.containsValue(matterDeviceId)) { + for (Map.Entry matterDevice : espApp.matterRmNodeIdMap.entrySet()) { + if (matterDeviceId.equals(matterDevice.getValue())) { + String rmNodeId = matterDevice.getKey(); + if (espApp.nodeMap.containsKey(rmNodeId)) { + + EspNode remoteNode = espApp.nodeMap.get(rmNodeId); + + int nodeStatus = remoteNode.getNodeStatus(); + if (nodeStatus != AppConstants.NODE_STATUS_MATTER_LOCAL && nodeStatus != AppConstants.NODE_STATUS_LOCAL) { + + if (enabled && reachable) { + Log.d(TAG, "Set Node status to remotely controllable for node id : " + rmNodeId); + remoteNode.setNodeStatus(AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE); + } + } + + JSONObject endpoints = deviceJson.optJSONObject(ESPControllerAPIKeys.KEY_ENDPOINTS); + if (endpoints != null) { + + JSONObject endpoint_1 = endpoints.optJSONObject(ESPControllerAPIKeys.ENDPOINT_ID_1); + if (endpoint_1 != null) { + + JSONObject clusters = endpoint_1.optJSONObject(ESPControllerAPIKeys.KEY_CLUSTERS); + if (clusters != null) { + + ArrayList params = remoteNode.getDevices().get(0).getParams(); + + for (Param p : params) { + + if (AppConstants.PARAM_TYPE_POWER.equals(p.getParamType())) { + // On off cluster + JSONObject onOffCluster = clusters.optJSONObject(ESPControllerAPIKeys.CLUSTER_ID_ON_OFF); + + if (onOffCluster != null) { + String value = onOffCluster.optString(ESPControllerAPIKeys.ATTRIBUTE_ID_ON_OFF); + boolean isPowerOn = false; + if (!TextUtils.isEmpty(value) && value.equals("1")) { + isPowerOn = true; + } + p.setSwitchStatus(isPowerOn); + } + } else if (AppConstants.PARAM_TYPE_BRIGHTNESS.equals(p.getParamType())) { + // Level cluster + JSONObject levelCluster = clusters.optJSONObject(ESPControllerAPIKeys.CLUSTER_ID_LEVEL_CONTROL); + + if (levelCluster != null) { + String value = levelCluster.optString(ESPControllerAPIKeys.ATTRIBUTE_ID_BRIGHTNESS_LEVEL); + if (!TextUtils.isEmpty(value)) { + int brightness = Integer.valueOf(value); + p.setValue(brightness); + } + } + } else if (AppConstants.PARAM_TYPE_HUE.equals(p.getParamType()) + || AppConstants.PARAM_TYPE_SATURATION.equals(p.getParamType())) { + + // Color cluster + JSONObject colorCluster = clusters.optJSONObject(ESPControllerAPIKeys.CLUSTER_ID_COLOR_CONTROL); + + if (colorCluster != null) { + String hueValue = colorCluster.optString(ESPControllerAPIKeys.ATTRIBUTE_ID_CURRENT_HUE); + String saturationValue = colorCluster.optString(ESPControllerAPIKeys.ATTRIBUTE_ID_CURRENT_SATURATION); + + if (!TextUtils.isEmpty(hueValue)) { + int hue = Integer.valueOf(hueValue); + p.setValue(hue); + } + + if (!TextUtils.isEmpty(saturationValue)) { + int saturation = Integer.valueOf(saturationValue); + p.setValue(saturation); + } + } + } + } + } + } + } + } + } + } + } + } catch (JSONException e) { + e.printStackTrace(); + } } } } diff --git a/app/src/main/java/com/espressif/cloudapi/ApiManager.java b/app/src/main/java/com/espressif/cloudapi/ApiManager.java index 6d4b9bc..61ffc07 100644 --- a/app/src/main/java/com/espressif/cloudapi/ApiManager.java +++ b/app/src/main/java/com/espressif/cloudapi/ApiManager.java @@ -65,6 +65,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -1116,7 +1117,6 @@ public void onResponse(Call call, Response response) Log.d(TAG, "Ignore config values for local node :" + nodeId); } - espNode.setOnline(true); espNode.setConfigData(configJson.toString()); espApp.nodeMap.put(nodeId, espNode); } @@ -1133,6 +1133,7 @@ public void onResponse(Call call, Response response) JSONObject sceneJson = paramsJson.optJSONObject(AppConstants.KEY_SCENES); JSONObject timeJson = paramsJson.optJSONObject(AppConstants.KEY_TIME); JSONObject localControlJson = paramsJson.optJSONObject(AppConstants.KEY_LOCAL_CONTROL); + JSONObject controllerJson = paramsJson.optJSONObject(AppConstants.KEY_MATTER_CONTROLLER); // If node is available on local network then ignore param values received from cloud. if (!espApp.localDeviceMap.containsKey(nodeId) && devices != null) { @@ -1520,38 +1521,58 @@ public void onResponse(Call call, Response response) } else { Log.e(TAG, "Local control JSON is not available"); } - } - // Node Status - JSONObject statusJson = nodeJson.optJSONObject(AppConstants.KEY_STATUS); + // Matter controller + if (controllerJson != null && services != null) { - String matterNodeId = ""; - boolean isMatterDeviceOnline = false; - if (espApp.matterRmNodeIdMap.containsKey(nodeId)) { - matterNodeId = espApp.matterRmNodeIdMap.get(nodeId); - } + for (Service service : services) { - if (!TextUtils.isEmpty(matterNodeId) && espApp.availableMatterDevices.contains(matterNodeId)) { - isMatterDeviceOnline = true; - } + if (AppConstants.SERVICE_TYPE_MATTER_CONTROLLER.equals(service.getType())) { - if (statusJson != null && !espApp.localDeviceMap.containsKey(nodeId) && !isMatterDeviceOnline) { + ArrayList controllerParams = service.getParams(); - JSONObject connectivityObject = statusJson.optJSONObject(AppConstants.KEY_CONNECTIVITY); + if (controllerParams != null) { - if (connectivityObject != null) { + for (Param controllerParam : controllerParams) { - boolean nodeStatus = connectivityObject.optBoolean(AppConstants.KEY_CONNECTED); - long timestamp = connectivityObject.optLong(AppConstants.KEY_TIMESTAMP); - espNode.setTimeStampOfStatus(timestamp); + String type = controllerParam.getParamType(); - if (espNode.isOnline() != nodeStatus) { - espNode.setOnline(nodeStatus); + if (!TextUtils.isEmpty(type) && AppConstants.PARAM_TYPE_MATTER_CTRL_DATA_VERSION.equals(type)) { + + controllerParam.setLabelValue(timeJson.optString(controllerParam.getName())); + + } else if (!TextUtils.isEmpty(type) && AppConstants.PARAM_TYPE_MATTER_DEVICES.equals(type)) { + + JSONObject matterDevicesJson = controllerJson.getJSONObject(controllerParam.getName()); + Iterator keys = matterDevicesJson.keys(); + HashMap matterDevices = new HashMap<>(); + + while (keys.hasNext()) { + String matterDeviceId = keys.next(); + JSONObject matterDeviceJson = matterDevicesJson.optJSONObject(matterDeviceId); + + if (matterDeviceId != null && matterDeviceJson != null) { + String value = matterDeviceJson.toString(); + matterDevices.put(matterDeviceId, value); + } + } + + if (!matterDevices.isEmpty()) { + espApp.controllerDevices.put(nodeId, matterDevices); + } + } + } + } + } } } else { - Log.e(TAG, "Connectivity object is null"); + Log.e(TAG, "Matter controller JSON is not available"); } } + + // Node Status + JSONObject statusJson = nodeJson.optJSONObject(AppConstants.KEY_STATUS); + setDeviceConnectivity(nodeId, espNode, statusJson); espDatabase.getNodeDao().insertOrUpdate(espNode); } } @@ -1637,6 +1658,60 @@ public void onFailure(Call call, Throwable t) { }); } + private void setDeviceConnectivity(String nodeId, EspNode espNode, JSONObject statusJson) { + + String matterNodeId = ""; + if (espApp.matterRmNodeIdMap.containsKey(nodeId)) { + matterNodeId = espApp.matterRmNodeIdMap.get(nodeId); + } + + if (!TextUtils.isEmpty(matterNodeId) && espNode.getNodeStatus() != AppConstants.NODE_STATUS_MATTER_LOCAL) { + + for (Map.Entry> entry : espApp.controllerDevices.entrySet()) { + + String key = entry.getKey(); + HashMap controllerDevices = entry.getValue(); + + if (controllerDevices.containsKey(matterNodeId)) { + String jsonStr = controllerDevices.get(matterNodeId); + if (jsonStr != null) { + try { + JSONObject deviceJson = new JSONObject(jsonStr); + boolean enabled = deviceJson.optBoolean(AppConstants.KEY_ENABLED); + boolean reachable = deviceJson.optBoolean(AppConstants.KEY_REACHABLE); + + if (enabled && reachable) { + espNode.setNodeStatus(AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + break; + } + } + } + + if (statusJson != null && !Arrays.asList(AppConstants.NODE_STATUS_LOCAL, AppConstants.NODE_STATUS_MATTER_LOCAL, + AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE).contains(espNode.getNodeStatus())) { + + JSONObject connectivityObject = statusJson.optJSONObject(AppConstants.KEY_CONNECTIVITY); + + if (connectivityObject != null) { + + boolean nodeStatus = connectivityObject.optBoolean(AppConstants.KEY_CONNECTED); + long timestamp = connectivityObject.optLong(AppConstants.KEY_TIMESTAMP); + espNode.setTimeStampOfStatus(timestamp); + + if (espNode.isOnline() != nodeStatus) { + espNode.setOnline(nodeStatus); + } + } else { + Log.e(TAG, "Connectivity object is null"); + } + } + } + private boolean isParamAvailableInList(ArrayList params, String type) { boolean isAvailable = false; if (params.size() > 0) { @@ -1729,25 +1804,8 @@ public void getNodeDetails(String nodeId) { // Node Status JSONObject statusJson = nodeJson.optJSONObject(AppConstants.KEY_STATUS); - - if (statusJson != null) { - - JSONObject connectivityObject = statusJson.optJSONObject(AppConstants.KEY_CONNECTIVITY); - - if (connectivityObject != null) { - - boolean nodeStatus = connectivityObject.optBoolean(AppConstants.KEY_CONNECTED); - long timestamp = connectivityObject.optLong(AppConstants.KEY_TIMESTAMP); - espNode.setTimeStampOfStatus(timestamp); - - if (espNode.isOnline() != nodeStatus) { - espNode.setOnline(nodeStatus); - EventBus.getDefault().post(new UpdateEvent(UpdateEventType.EVENT_DEVICE_STATUS_UPDATE)); - } - } else { - Log.e(TAG, "Connectivity object is null"); - } - } + setDeviceConnectivity(nodeId, espNode, statusJson); + EventBus.getDefault().post(new UpdateEvent(UpdateEventType.EVENT_DEVICE_STATUS_UPDATE)); } } } @@ -3110,6 +3168,7 @@ private void setFabricGroupDetails(String jsonResponse) { JSONObject nodeDetailJson = nodeDetailsArray.optJSONObject(nodeIndex); String nodeId = nodeDetailJson.optString(AppConstants.KEY_NODE_ID); String matterNodeId = nodeDetailJson.optString(AppConstants.KEY_MATTER_NODE_ID); + matterNodeId = matterNodeId.toLowerCase(); if (!TextUtils.isEmpty(nodeId) && !TextUtils.isEmpty(matterNodeId)) { nodeDetails.put(nodeId, matterNodeId); diff --git a/app/src/main/java/com/espressif/matter/RemoteControlApiHelper.java b/app/src/main/java/com/espressif/matter/RemoteControlApiHelper.java new file mode 100644 index 0000000..f891268 --- /dev/null +++ b/app/src/main/java/com/espressif/matter/RemoteControlApiHelper.java @@ -0,0 +1,604 @@ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.espressif.matter; + +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; + +import com.espressif.AppConstants; +import com.espressif.ESPControllerAPIKeys; +import com.espressif.EspApplication; +import com.espressif.cloudapi.ApiManager; +import com.espressif.cloudapi.ApiResponseListener; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Iterator; + +public class RemoteControlApiHelper { + + private static final String TAG = "RemoteControlApiHelper"; + + private EspApplication espApp; + + public RemoteControlApiHelper(EspApplication context) { + espApp = context; + } + + public void callOffAPI(String controllerNodeId, String matterNodeId, String paramName, + String serviceName, String endpointId, ApiResponseListener listener) { + + JsonObject jsonCommand = new JsonObject(); + jsonCommand.addProperty(ESPControllerAPIKeys.KEY_COMMAND_ID, ESPControllerAPIKeys.COMMAND_ID_OFF); + + JsonArray commands = new JsonArray(); + commands.add(jsonCommand); + + JsonObject cluster = new JsonObject(); + cluster.addProperty(ESPControllerAPIKeys.KEY_CLUSTER_ID, ESPControllerAPIKeys.CLUSTER_ID_ON_OFF); + cluster.add(ESPControllerAPIKeys.KEY_COMMANDS, commands); + + JsonArray clusters = new JsonArray(); + clusters.add(cluster); + + JsonObject endpt = new JsonObject(); + endpt.addProperty(ESPControllerAPIKeys.KEY_ENDPOINT_ID, endpointId); + endpt.add(ESPControllerAPIKeys.KEY_CLUSTERS, clusters); + + JsonArray endpoints = new JsonArray(); + endpoints.add(endpt); + + JsonObject matterNode = new JsonObject(); + matterNode.addProperty(ESPControllerAPIKeys.KEY_MATTER_NODE_ID, matterNodeId); + matterNode.add(ESPControllerAPIKeys.KEY_ENDPOINTS, endpoints); + + JsonArray matterNodes = new JsonArray(); + matterNodes.add(matterNode); + + JsonObject matterDevices = new JsonObject(); + matterDevices.add(ESPControllerAPIKeys.KEY_MATTER_NODES, matterNodes); + + JsonObject matterDevicesJson = new JsonObject(); + matterDevicesJson.add(paramName, matterDevices); + + JsonObject serviceJson = new JsonObject(); + serviceJson.add(serviceName, matterDevicesJson); + + ApiManager apiManager = ApiManager.getInstance(espApp); + + apiManager.updateParamValue(controllerNodeId, serviceJson, new ApiResponseListener() { + + @Override + public void onSuccess(Bundle data) { + listener.onSuccess(data); + } + + @Override + public void onResponseFailure(Exception exception) { + listener.onResponseFailure(exception); + } + + @Override + public void onNetworkFailure(Exception exception) { + listener.onNetworkFailure(exception); + } + }); + } + + public void callOnAPI(String controllerNodeId, String matterNodeId, String paramName, + String serviceName, String endpointId, ApiResponseListener listener) { + + JsonObject jsonCommand = new JsonObject(); + jsonCommand.addProperty(ESPControllerAPIKeys.KEY_COMMAND_ID, ESPControllerAPIKeys.COMMAND_ID_ON); + + JsonArray commands = new JsonArray(); + commands.add(jsonCommand); + + JsonObject cluster = new JsonObject(); + cluster.addProperty(ESPControllerAPIKeys.KEY_CLUSTER_ID, ESPControllerAPIKeys.CLUSTER_ID_ON_OFF); + cluster.add(ESPControllerAPIKeys.KEY_COMMANDS, commands); + + JsonArray clusters = new JsonArray(); + clusters.add(cluster); + + JsonObject endpt = new JsonObject(); + endpt.addProperty(ESPControllerAPIKeys.KEY_ENDPOINT_ID, endpointId); + endpt.add(ESPControllerAPIKeys.KEY_CLUSTERS, clusters); + + JsonArray endpoints = new JsonArray(); + endpoints.add(endpt); + + JsonObject matterNode = new JsonObject(); + matterNode.addProperty(ESPControllerAPIKeys.KEY_MATTER_NODE_ID, matterNodeId); + matterNode.add(ESPControllerAPIKeys.KEY_ENDPOINTS, endpoints); + + JsonArray matterNodes = new JsonArray(); + matterNodes.add(matterNode); + + JsonObject matterDevices = new JsonObject(); + matterDevices.add(ESPControllerAPIKeys.KEY_MATTER_NODES, matterNodes); + + JsonObject matterDevicesJson = new JsonObject(); + matterDevicesJson.add(paramName, matterDevices); + + JsonObject serviceJson = new JsonObject(); + serviceJson.add(serviceName, matterDevicesJson); + + ApiManager apiManager = ApiManager.getInstance(espApp); + + apiManager.updateParamValue(controllerNodeId, serviceJson, new ApiResponseListener() { + + @Override + public void onSuccess(Bundle data) { + listener.onSuccess(data); + } + + @Override + public void onResponseFailure(Exception exception) { + listener.onResponseFailure(exception); + } + + @Override + public void onNetworkFailure(Exception exception) { + listener.onNetworkFailure(exception); + } + }); + } + + public void callToggleAPI(String controllerNodeId, String matterNodeId, String paramName, + String serviceName, String endpointId, ApiResponseListener listener) { + + JsonObject jsonCommand = new JsonObject(); + jsonCommand.addProperty(ESPControllerAPIKeys.KEY_COMMAND_ID, ESPControllerAPIKeys.COMMAND_ID_TOGGLE); + + JsonArray commands = new JsonArray(); + commands.add(jsonCommand); + + JsonObject cluster = new JsonObject(); + cluster.addProperty(ESPControllerAPIKeys.KEY_CLUSTER_ID, ESPControllerAPIKeys.CLUSTER_ID_ON_OFF); + cluster.add(ESPControllerAPIKeys.KEY_COMMANDS, commands); + + JsonArray clusters = new JsonArray(); + clusters.add(cluster); + + JsonObject endpt = new JsonObject(); + endpt.addProperty(ESPControllerAPIKeys.KEY_ENDPOINT_ID, endpointId); + endpt.add(ESPControllerAPIKeys.KEY_CLUSTERS, clusters); + + JsonArray endpoints = new JsonArray(); + endpoints.add(endpt); + + JsonObject matterNode = new JsonObject(); + matterNode.addProperty(ESPControllerAPIKeys.KEY_MATTER_NODE_ID, matterNodeId); + matterNode.add(ESPControllerAPIKeys.KEY_ENDPOINTS, endpoints); + + JsonArray matterNodes = new JsonArray(); + matterNodes.add(matterNode); + + JsonObject matterDevices = new JsonObject(); + matterDevices.add(ESPControllerAPIKeys.KEY_MATTER_NODES, matterNodes); + + JsonObject matterDevicesJson = new JsonObject(); + matterDevicesJson.add(paramName, matterDevices); + + JsonObject serviceJson = new JsonObject(); + serviceJson.add(serviceName, matterDevicesJson); + + ApiManager apiManager = ApiManager.getInstance(espApp); + + apiManager.updateParamValue(controllerNodeId, serviceJson, new ApiResponseListener() { + + @Override + public void onSuccess(Bundle data) { + listener.onSuccess(data); + } + + @Override + public void onResponseFailure(Exception exception) { + listener.onResponseFailure(exception); + } + + @Override + public void onNetworkFailure(Exception exception) { + listener.onNetworkFailure(exception); + } + }); + } + + public void callBrightnessAPI(String controllerNodeId, String matterNodeId, String paramName, + String serviceName, String endpointId, int brightness, ApiResponseListener listener) { + + JsonObject data = new JsonObject(); + data.addProperty("0:U8", brightness); + data.addProperty("1:U16", 0); + data.addProperty("2:U8", 0); + data.addProperty("3:U8", 0); + + JsonObject jsonCommand = new JsonObject(); + jsonCommand.addProperty(ESPControllerAPIKeys.KEY_COMMAND_ID, ESPControllerAPIKeys.COMMAND_ID_MOVE_TO_LEVEL_WITH_ON_OFF); + jsonCommand.add(ESPControllerAPIKeys.KEY_DATA, data); + + JsonArray commands = new JsonArray(); + commands.add(jsonCommand); + + JsonObject cluster = new JsonObject(); + cluster.addProperty(ESPControllerAPIKeys.KEY_CLUSTER_ID, ESPControllerAPIKeys.CLUSTER_ID_LEVEL_CONTROL); + cluster.add(ESPControllerAPIKeys.KEY_COMMANDS, commands); + + JsonArray clusters = new JsonArray(); + clusters.add(cluster); + + JsonObject endpt = new JsonObject(); + endpt.addProperty(ESPControllerAPIKeys.KEY_ENDPOINT_ID, endpointId); + endpt.add(ESPControllerAPIKeys.KEY_CLUSTERS, clusters); + + JsonArray endpoints = new JsonArray(); + endpoints.add(endpt); + + JsonObject matterNode = new JsonObject(); + matterNode.addProperty(ESPControllerAPIKeys.KEY_MATTER_NODE_ID, matterNodeId); + matterNode.add(ESPControllerAPIKeys.KEY_ENDPOINTS, endpoints); + + JsonArray matterNodes = new JsonArray(); + matterNodes.add(matterNode); + + JsonObject matterDevices = new JsonObject(); + matterDevices.add(ESPControllerAPIKeys.KEY_MATTER_NODES, matterNodes); + + JsonObject matterDevicesJson = new JsonObject(); + matterDevicesJson.add(paramName, matterDevices); + + JsonObject serviceJson = new JsonObject(); + serviceJson.add(serviceName, matterDevicesJson); + + ApiManager apiManager = ApiManager.getInstance(espApp); + + apiManager.updateParamValue(controllerNodeId, serviceJson, new ApiResponseListener() { + + @Override + public void onSuccess(Bundle data) { + listener.onSuccess(data); + } + + @Override + public void onResponseFailure(Exception exception) { + listener.onResponseFailure(exception); + } + + @Override + public void onNetworkFailure(Exception exception) { + listener.onNetworkFailure(exception); + } + }); + } + + public void callSaturationAPI(String controllerNodeId, String matterNodeId, String paramName, + String serviceName, String endpointId, int saturation, ApiResponseListener listener) { + + JsonObject data = new JsonObject(); + data.addProperty("0:U8", saturation); + data.addProperty("1:U16", 0); + data.addProperty("2:U8", 0); + data.addProperty("3:U8", 0); + + JsonObject jsonCommand = new JsonObject(); + jsonCommand.addProperty(ESPControllerAPIKeys.KEY_COMMAND_ID, ESPControllerAPIKeys.COMMAND_ID_MOVE_TO_SATURATION); + jsonCommand.add(ESPControllerAPIKeys.KEY_DATA, data); + + JsonArray commands = new JsonArray(); + commands.add(jsonCommand); + + JsonObject cluster = new JsonObject(); + cluster.addProperty(ESPControllerAPIKeys.KEY_CLUSTER_ID, ESPControllerAPIKeys.CLUSTER_ID_COLOR_CONTROL); + cluster.add(ESPControllerAPIKeys.KEY_COMMANDS, commands); + + JsonArray clusters = new JsonArray(); + clusters.add(cluster); + + JsonObject endpt = new JsonObject(); + endpt.addProperty(ESPControllerAPIKeys.KEY_ENDPOINT_ID, endpointId); + endpt.add(ESPControllerAPIKeys.KEY_CLUSTERS, clusters); + + JsonArray endpoints = new JsonArray(); + endpoints.add(endpt); + + JsonObject matterNode = new JsonObject(); + matterNode.addProperty(ESPControllerAPIKeys.KEY_MATTER_NODE_ID, matterNodeId); + matterNode.add(ESPControllerAPIKeys.KEY_ENDPOINTS, endpoints); + + JsonArray matterNodes = new JsonArray(); + matterNodes.add(matterNode); + + JsonObject matterDevices = new JsonObject(); + matterDevices.add(ESPControllerAPIKeys.KEY_MATTER_NODES, matterNodes); + + JsonObject matterDevicesJson = new JsonObject(); + matterDevicesJson.add(paramName, matterDevices); + + JsonObject serviceJson = new JsonObject(); + serviceJson.add(serviceName, matterDevicesJson); + + ApiManager apiManager = ApiManager.getInstance(espApp); + + apiManager.updateParamValue(controllerNodeId, serviceJson, new ApiResponseListener() { + + @Override + public void onSuccess(Bundle data) { + listener.onSuccess(data); + } + + @Override + public void onResponseFailure(Exception exception) { + listener.onResponseFailure(exception); + } + + @Override + public void onNetworkFailure(Exception exception) { + listener.onNetworkFailure(exception); + } + }); + } + + public void callHueAPI(String controllerNodeId, String matterNodeId, String paramName, + String serviceName, String endpointId, int hueValue, ApiResponseListener listener) { + + JsonObject data = new JsonObject(); + data.addProperty("0:U8", hueValue); + data.addProperty("1:U16", 0); + data.addProperty("2:U16", 0); + data.addProperty("3:U8", 0); + data.addProperty("4:U8", 0); + + JsonObject jsonCommand = new JsonObject(); + jsonCommand.addProperty(ESPControllerAPIKeys.KEY_COMMAND_ID, ESPControllerAPIKeys.COMMAND_ID_MOVE_TO_HUE); + jsonCommand.add(ESPControllerAPIKeys.KEY_DATA, data); + + JsonArray commands = new JsonArray(); + commands.add(jsonCommand); + + JsonObject cluster = new JsonObject(); + cluster.addProperty(ESPControllerAPIKeys.KEY_CLUSTER_ID, ESPControllerAPIKeys.CLUSTER_ID_COLOR_CONTROL); + cluster.add(ESPControllerAPIKeys.KEY_COMMANDS, commands); + + JsonArray clusters = new JsonArray(); + clusters.add(cluster); + + JsonObject endpt = new JsonObject(); + endpt.addProperty(ESPControllerAPIKeys.KEY_ENDPOINT_ID, endpointId); + endpt.add(ESPControllerAPIKeys.KEY_CLUSTERS, clusters); + + JsonArray endpoints = new JsonArray(); + endpoints.add(endpt); + + JsonObject matterNode = new JsonObject(); + matterNode.addProperty(ESPControllerAPIKeys.KEY_MATTER_NODE_ID, matterNodeId); + matterNode.add(ESPControllerAPIKeys.KEY_ENDPOINTS, endpoints); + + JsonArray matterNodes = new JsonArray(); + matterNodes.add(matterNode); + + JsonObject matterDevices = new JsonObject(); + matterDevices.add(ESPControllerAPIKeys.KEY_MATTER_NODES, matterNodes); + + JsonObject matterDevicesJson = new JsonObject(); + matterDevicesJson.add(paramName, matterDevices); + + JsonObject serviceJson = new JsonObject(); + serviceJson.add(serviceName, matterDevicesJson); + + ApiManager apiManager = ApiManager.getInstance(espApp); + + apiManager.updateParamValue(controllerNodeId, serviceJson, new ApiResponseListener() { + + @Override + public void onSuccess(Bundle data) { + listener.onSuccess(data); + } + + @Override + public void onResponseFailure(Exception exception) { + listener.onResponseFailure(exception); + } + + @Override + public void onNetworkFailure(Exception exception) { + listener.onNetworkFailure(exception); + } + }); + } + + /// Get on off cluster value + /// - Parameters: + /// - controllerNodeId: controller node id + /// - matterNodeId: matter node id + /// - Returns: on/off status + public boolean getOnOffValue(String controllerNodeId, String matterNodeId) { + + if (espApp.controllerDevices.containsKey(controllerNodeId)) { + HashMap matterDevices = espApp.controllerDevices.get(controllerNodeId); + if (matterDevices.containsKey(matterNodeId)) { + String myJson = matterDevices.get(matterNodeId); + + try { + JSONObject matterDeviceJson = new JSONObject(myJson); + if (matterDeviceJson != null) { + + JSONObject endpointsJson = matterDeviceJson.optJSONObject(AppConstants.KEY_ENDPOINTS); + Iterator endpointKeys = endpointsJson.keys(); + + while (endpointKeys.hasNext()) { + String endpointId = endpointKeys.next(); + JSONObject endpointJson = matterDeviceJson.optJSONObject(endpointId); + + if (endpointJson != null) { + JSONObject tempJson = endpointJson.optJSONObject(ESPControllerAPIKeys.ENDPOINT_ID_1); + + if (tempJson != null) { + JSONObject clusters = tempJson.optJSONObject("clusters"); + JSONObject onOffCLuster = clusters.optJSONObject(ESPControllerAPIKeys.CLUSTER_ID_ON_OFF); + String attrValue = onOffCLuster.getString(ESPControllerAPIKeys.ATTRIBUTE_ID_ON_OFF); + if (attrValue != null && attrValue.equals("1")) { + return true; + } else { + return false; + } + } + } + } + } + + } catch (JSONException e) { + e.printStackTrace(); + return false; + } + } + } + return false; + } + + /// Get brightness level + /// - Parameters: + /// - controllerNodeId: controller node id + /// - matterNodeId: matter node id + /// - Returns: brightness level + public int getBrightnessLevel(String controllerNodeId, String matterNodeId) { + + if (espApp.controllerDevices.containsKey(controllerNodeId)) { + HashMap matterDevices = espApp.controllerDevices.get(controllerNodeId); + if (matterDevices.containsKey(matterNodeId)) { + String myJson = matterDevices.get(matterNodeId); + + try { + JSONObject matterDeviceJson = new JSONObject(myJson); + if (matterDeviceJson != null) { + + JSONObject endpointsJson = matterDeviceJson.optJSONObject(AppConstants.KEY_ENDPOINTS); + Iterator endpointKeys = endpointsJson.keys(); + + while (endpointKeys.hasNext()) { + String endpointId = endpointKeys.next(); + JSONObject endpointJson = matterDeviceJson.optJSONObject(endpointId); + + if (endpointJson != null) { + JSONObject tempJson = endpointJson.optJSONObject(ESPControllerAPIKeys.ENDPOINT_ID_1); + + if (tempJson != null) { + JSONObject clusters = tempJson.optJSONObject("clusters"); + JSONObject onOffCLuster = clusters.optJSONObject(ESPControllerAPIKeys.CLUSTER_ID_LEVEL_CONTROL); + String attrValue = onOffCLuster.getString(ESPControllerAPIKeys.ATTRIBUTE_ID_BRIGHTNESS_LEVEL); + return Integer.valueOf(attrValue); + } + } + } + } + + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + } + return 0; + } + + public int getCurrentHue(String controllerNodeId, String matterNodeId) { + + if (espApp.controllerDevices.containsKey(controllerNodeId)) { + HashMap matterDevices = espApp.controllerDevices.get(controllerNodeId); + if (matterDevices.containsKey(matterNodeId)) { + String myJson = matterDevices.get(matterNodeId); + + try { + JSONObject matterDeviceJson = new JSONObject(myJson); + if (matterDeviceJson != null) { + + JSONObject endpointsJson = matterDeviceJson.optJSONObject(AppConstants.KEY_ENDPOINTS); + Iterator endpointKeys = endpointsJson.keys(); + + while (endpointKeys.hasNext()) { + String endpointId = endpointKeys.next(); + JSONObject endpointJson = matterDeviceJson.optJSONObject(endpointId); + + if (endpointJson != null) { + JSONObject tempJson = endpointJson.optJSONObject(ESPControllerAPIKeys.ENDPOINT_ID_1); + + if (tempJson != null) { + JSONObject clusters = tempJson.optJSONObject("clusters"); + JSONObject onOffCLuster = clusters.optJSONObject(ESPControllerAPIKeys.CLUSTER_ID_COLOR_CONTROL); + String attrValue = onOffCLuster.getString(ESPControllerAPIKeys.ATTRIBUTE_ID_CURRENT_HUE); + if (!TextUtils.isEmpty(attrValue)) { + return Integer.valueOf(attrValue); + } + } + } + } + } + + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + } + return 0; + } + + public int getCurrentSaturation(String controllerNodeId, String matterNodeId) { + + if (espApp.controllerDevices.containsKey(controllerNodeId)) { + HashMap matterDevices = espApp.controllerDevices.get(controllerNodeId); + if (matterDevices.containsKey(matterNodeId)) { + String myJson = matterDevices.get(matterNodeId); + + try { + JSONObject matterDeviceJson = new JSONObject(myJson); + if (matterDeviceJson != null) { + + JSONObject endpointsJson = matterDeviceJson.optJSONObject(AppConstants.KEY_ENDPOINTS); + Iterator endpointKeys = endpointsJson.keys(); + + while (endpointKeys.hasNext()) { + String endpointId = endpointKeys.next(); + JSONObject endpointJson = matterDeviceJson.optJSONObject(endpointId); + + if (endpointJson != null) { + JSONObject tempJson = endpointJson.optJSONObject(ESPControllerAPIKeys.ENDPOINT_ID_1); + + if (tempJson != null) { + JSONObject clusters = tempJson.optJSONObject("clusters"); + JSONObject onOffCLuster = clusters.optJSONObject(ESPControllerAPIKeys.CLUSTER_ID_COLOR_CONTROL); + String attrValue = onOffCLuster.getString(ESPControllerAPIKeys.ATTRIBUTE_ID_CURRENT_SATURATION); + if (!TextUtils.isEmpty(attrValue)) { + return Integer.valueOf(attrValue); + } + } + } + } + } + + } catch (JSONException e) { + e.printStackTrace(); + return 0; + } + } + } + return 0; + } +} diff --git a/app/src/main/java/com/espressif/ui/activities/EspDeviceActivity.java b/app/src/main/java/com/espressif/ui/activities/EspDeviceActivity.java index 8268331..dd3a24c 100644 --- a/app/src/main/java/com/espressif/ui/activities/EspDeviceActivity.java +++ b/app/src/main/java/com/espressif/ui/activities/EspDeviceActivity.java @@ -63,9 +63,12 @@ import java.math.BigInteger; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; public class EspDeviceActivity extends AppCompatActivity { @@ -95,7 +98,6 @@ public class EspDeviceActivity extends AppCompatActivity { private ArrayList attributeList; private Handler handler; private ContentLoadingProgressBar progressBar; - private boolean isNodeOnline; private long timeStampOfStatus; private boolean isNetworkAvailable = true; private boolean shouldGetParams = true; @@ -106,6 +108,7 @@ public class EspDeviceActivity extends AppCompatActivity { private boolean isControllerClusterAvailable = false, isTbrClusterAvailable = false; private String nodeType, matterNodeId; + private int nodeStatus; @Override protected void onCreate(Bundle savedInstanceState) { @@ -125,8 +128,8 @@ protected void onCreate(Bundle savedInstanceState) { nodeId = device.getNodeId(); Log.d(TAG, "NODE ID : " + nodeId); - isNodeOnline = espApp.nodeMap.get(nodeId).isOnline(); nodeType = espApp.nodeMap.get(nodeId).getNewNodeType(); + nodeStatus = espApp.nodeMap.get(nodeId).getNodeStatus(); timeStampOfStatus = espApp.nodeMap.get(nodeId).getTimeStampOfStatus(); snackbar = Snackbar.make(findViewById(R.id.params_parent_layout), R.string.msg_no_internet, Snackbar.LENGTH_INDEFINITE); @@ -264,6 +267,13 @@ public void updateDeviceNameInTitle(String deviceName) { } public boolean isNodeOnline() { + boolean isNodeOnline = false; + + if (Arrays.asList(AppConstants.NODE_STATUS_ONLINE, AppConstants.NODE_STATUS_LOCAL, + AppConstants.NODE_STATUS_MATTER_LOCAL, AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE) + .contains(nodeStatus)) { + isNodeOnline = true; + } return isNodeOnline; } @@ -276,7 +286,8 @@ public void setLastUpdateRequestTime(long lastUpdateRequestTime) { } public void startUpdateValueTask() { - if (!TextUtils.isEmpty(nodeType) && nodeType.equals(AppConstants.NODE_TYPE_PURE_MATTER)) { + if (!TextUtils.isEmpty(nodeType) && nodeType.equals(AppConstants.NODE_TYPE_PURE_MATTER) + && nodeStatus != AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE) { return; } shouldGetParams = true; @@ -475,7 +486,34 @@ public void onNetworkFailure(Exception exception) { private void getValues() { - networkApiManager.getParamsValues(nodeId, new ApiResponseListener() { + if (nodeStatus == AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE) { + + if (!TextUtils.isEmpty(nodeType) && nodeType.equals(AppConstants.NODE_TYPE_PURE_MATTER)) { + + String controllerNodeId = ""; + + for (Map.Entry> entry : espApp.controllerDevices.entrySet()) { + + HashMap controllerDevices = entry.getValue(); + + if (controllerDevices.containsKey(matterNodeId)) { + controllerNodeId = entry.getKey(); + break; + } + } + + if (!TextUtils.isEmpty(controllerNodeId)) { + getParamValuesForDevice(controllerNodeId); + } + } + } else { + getParamValuesForDevice(nodeId); + } + } + + private void getParamValuesForDevice(String rmNodeId) { + + networkApiManager.getParamsValues(rmNodeId, new ApiResponseListener() { @Override public void onSuccess(Bundle data) { @@ -629,8 +667,8 @@ private void updateUi() { if (espApp.nodeMap.containsKey(nodeId)) { ArrayList devices = espApp.nodeMap.get(nodeId).getDevices(); - isNodeOnline = espApp.nodeMap.get(nodeId).isOnline(); timeStampOfStatus = espApp.nodeMap.get(nodeId).getTimeStampOfStatus(); + nodeStatus = espApp.nodeMap.get(nodeId).getNodeStatus(); for (int i = 0; i < devices.size(); i++) { @@ -663,29 +701,55 @@ private void updateUi() { setParamList(updatedDevice.getParams()); - if (!isNodeOnline) { + switch (nodeStatus) { + case AppConstants.NODE_STATUS_MATTER_LOCAL: - if (espApp.getAppState().equals(EspApplication.AppState.GET_DATA_SUCCESS)) { + if (espApp.getAppState().equals(EspApplication.AppState.GET_DATA_SUCCESS)) { + if (!TextUtils.isEmpty(matterNodeId) && espApp.availableMatterDevices.contains(matterNodeId) + && espApp.matterDeviceInfoMap.containsKey(matterNodeId)) { - rlNodeStatus.setVisibility(View.VISIBLE); + rlNodeStatus.setVisibility(View.VISIBLE); + tvNodeStatus.setText(R.string.status_local); + } + } else { + rlNodeStatus.setVisibility(View.INVISIBLE); + } + break; - if (espApp.localDeviceMap.containsKey(nodeId)) { + case AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE: - EspLocalDevice localDevice = espApp.localDeviceMap.get(nodeId); - if (localDevice.getSecurityType() == 1 || localDevice.getSecurityType() == 2) { - ivSecureLocal.setVisibility(View.VISIBLE); - } else { - ivSecureLocal.setVisibility(View.GONE); - } - tvNodeStatus.setText(R.string.local_device_text); + if (espApp.getAppState().equals(EspApplication.AppState.GET_DATA_SUCCESS)) { + rlNodeStatus.setVisibility(View.VISIBLE); + tvNodeStatus.setText(R.string.status_remote); + } else { + rlNodeStatus.setVisibility(View.INVISIBLE); + } + break; - } else if (!TextUtils.isEmpty(matterNodeId) && espApp.availableMatterDevices.contains(matterNodeId) - && espApp.matterDeviceInfoMap.containsKey(matterNodeId)) { + case AppConstants.NODE_STATUS_LOCAL: + + EspLocalDevice localDevice = espApp.localDeviceMap.get(nodeId); + + if (espApp.getAppState().equals(EspApplication.AppState.GET_DATA_SUCCESS)) { rlNodeStatus.setVisibility(View.VISIBLE); - tvNodeStatus.setText(R.string.status_local); + if (espApp.localDeviceMap.containsKey(nodeId)) { + + if (localDevice.getSecurityType() == 1 || localDevice.getSecurityType() == 2) { + ivSecureLocal.setVisibility(View.VISIBLE); + } else { + ivSecureLocal.setVisibility(View.GONE); + } + tvNodeStatus.setText(R.string.local_device_text); + } } else { + rlNodeStatus.setVisibility(View.INVISIBLE); + } + break; + + case AppConstants.NODE_STATUS_OFFLINE: + if (espApp.getAppState().equals(EspApplication.AppState.GET_DATA_SUCCESS)) { ivSecureLocal.setVisibility(View.GONE); String offlineText = getString(R.string.status_offline); tvNodeStatus.setText(offlineText); @@ -712,35 +776,15 @@ private void updateUi() { } tvNodeStatus.setText(offlineText); } - } - } else { - rlNodeStatus.setVisibility(View.INVISIBLE); - } - - } else { - - if (!TextUtils.isEmpty(matterNodeId) && espApp.availableMatterDevices.contains(matterNodeId) - && espApp.matterDeviceInfoMap.containsKey(matterNodeId)) { - - rlNodeStatus.setVisibility(View.VISIBLE); - tvNodeStatus.setText(R.string.status_local); - } else { - rlNodeStatus.setVisibility(View.INVISIBLE); - } - - if (espApp.localDeviceMap.containsKey(nodeId)) { - - rlNodeStatus.setVisibility(View.VISIBLE); - EspLocalDevice localDevice = espApp.localDeviceMap.get(nodeId); - if (localDevice.getSecurityType() == 1 || localDevice.getSecurityType() == 2) { - ivSecureLocal.setVisibility(View.VISIBLE); } else { - ivSecureLocal.setVisibility(View.GONE); + rlNodeStatus.setVisibility(View.INVISIBLE); } - tvNodeStatus.setText(R.string.local_device_text); - } else { + break; + + case AppConstants.NODE_STATUS_ONLINE: + default: rlNodeStatus.setVisibility(View.INVISIBLE); - } + break; } paramAdapter.updateParamList(paramList); diff --git a/app/src/main/java/com/espressif/ui/adapters/EspDeviceAdapter.java b/app/src/main/java/com/espressif/ui/adapters/EspDeviceAdapter.java index 71ab536..d8a992e 100644 --- a/app/src/main/java/com/espressif/ui/adapters/EspDeviceAdapter.java +++ b/app/src/main/java/com/espressif/ui/adapters/EspDeviceAdapter.java @@ -30,29 +30,36 @@ import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.recyclerview.widget.RecyclerView; import com.aar.tapholdupbutton.TapHoldUpButton; import com.espressif.AppConstants; +import com.espressif.ESPControllerAPIKeys; import com.espressif.EspApplication; import com.espressif.NetworkApiManager; import com.espressif.cloudapi.ApiResponseListener; import com.espressif.local_control.EspLocalDevice; import com.espressif.matter.ControllerLoginActivity; import com.espressif.matter.OnOffClusterHelper; +import com.espressif.matter.RemoteControlApiHelper; import com.espressif.rainmaker.R; import com.espressif.ui.Utils; import com.espressif.ui.activities.EspDeviceActivity; import com.espressif.ui.models.Device; import com.espressif.ui.models.EspNode; import com.espressif.ui.models.Param; +import com.espressif.ui.models.Service; +import com.espressif.utils.NodeUtils; import com.google.gson.JsonObject; import java.math.BigInteger; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; public class EspDeviceAdapter extends RecyclerView.Adapter { @@ -82,17 +89,13 @@ public void onBindViewHolder(@NonNull final DeviceViewHolder deviceVh, final int EspApplication espApp = (EspApplication) context.getApplicationContext(); EspNode node = espApp.nodeMap.get(device.getNodeId()); String deviceName = device.getUserVisibleName(); - boolean isMatterDeviceOnline = false; String nodeId = device.getNodeId(); String matterNodeId = ""; + int nodeStatus = espApp.nodeMap.get(device.getNodeId()).getNodeStatus(); if (espApp.matterRmNodeIdMap.containsKey(nodeId)) { matterNodeId = espApp.matterRmNodeIdMap.get(nodeId); } - if (!TextUtils.isEmpty(matterNodeId) && espApp.availableMatterDevices.contains(matterNodeId)) { - isMatterDeviceOnline = true; - } - // set the data in items if (TextUtils.isEmpty(deviceName)) { deviceName = device.getDeviceName(); @@ -144,7 +147,6 @@ public void onBindViewHolder(@NonNull final DeviceViewHolder deviceVh, final int if (param.getProperties().contains(AppConstants.KEY_PROPERTY_WRITE)) { - boolean finalIsMatterDeviceOnline = isMatterDeviceOnline; String finalMatterNodeId = matterNodeId; deviceVh.ivDeviceStatus.setOnClickListener(new View.OnClickListener() { @@ -160,37 +162,98 @@ public void onClick(View v) { deviceVh.ivDeviceStatus.setImageResource(R.drawable.ic_output_on); } - if (finalIsMatterDeviceOnline) { - - BigInteger id = new BigInteger(finalMatterNodeId, 16); - long deviceId = id.longValue(); - OnOffClusterHelper espClusterHelper = new OnOffClusterHelper(espApp.chipClientMap.get(finalMatterNodeId)); - espClusterHelper.setOnOffDeviceStateOnOffClusterAsync(deviceId, !status, AppConstants.ENDPOINT_1); - param.setSwitchStatus(!status); - - } else { - JsonObject jsonParam = new JsonObject(); - JsonObject body = new JsonObject(); - jsonParam.addProperty(param.getName(), !status); - body.add(device.getDeviceName(), jsonParam); - - networkApiManager.updateParamValue(device.getNodeId(), body, new ApiResponseListener() { - - @Override - public void onSuccess(Bundle data) { - param.setSwitchStatus(!status); - } - - @Override - public void onResponseFailure(Exception exception) { - Toast.makeText(context, R.string.error_param_update, Toast.LENGTH_SHORT).show(); - } - - @Override - public void onNetworkFailure(Exception exception) { - Toast.makeText(context, R.string.error_param_update, Toast.LENGTH_SHORT).show(); + switch (nodeStatus) { + case AppConstants.NODE_STATUS_MATTER_LOCAL: + BigInteger id = new BigInteger(finalMatterNodeId, 16); + long deviceId = id.longValue(); + OnOffClusterHelper espClusterHelper = new OnOffClusterHelper(espApp.chipClientMap.get(finalMatterNodeId)); + espClusterHelper.setOnOffDeviceStateOnOffClusterAsync(deviceId, !status, AppConstants.ENDPOINT_1); + param.setSwitchStatus(!status); + break; + + case AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE: + RemoteControlApiHelper apiHelper = new RemoteControlApiHelper(espApp); + + for (Map.Entry> entry : espApp.controllerDevices.entrySet()) { + + HashMap controllerDevices = entry.getValue(); + + if (controllerDevices.containsKey(finalMatterNodeId)) { + + EspNode espNode = espApp.nodeMap.get(entry.getKey()); + Service controllerService = NodeUtils.Companion.getService(espNode, AppConstants.SERVICE_TYPE_MATTER_CONTROLLER); + + if (controllerService != null) { + for (Param deviceParam : controllerService.getParams()) { + if (AppConstants.PARAM_TYPE_MATTER_DEVICES.equals(deviceParam.getParamType())) { + + if (status) { + apiHelper.callOffAPI(entry.getKey(), finalMatterNodeId, deviceParam.getName(), controllerService.getName(), ESPControllerAPIKeys.ENDPOINT_ID_1, new ApiResponseListener() { + @Override + public void onSuccess(@Nullable Bundle data) { + param.setSwitchStatus(!status); + } + + @Override + public void onResponseFailure(@NonNull Exception exception) { + Toast.makeText(context, R.string.error_param_update, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNetworkFailure(@NonNull Exception exception) { + Toast.makeText(context, R.string.error_param_update, Toast.LENGTH_SHORT).show(); + } + }); + } else { + apiHelper.callOnAPI(entry.getKey(), finalMatterNodeId, deviceParam.getName(), controllerService.getName(), ESPControllerAPIKeys.ENDPOINT_ID_1, new ApiResponseListener() { + @Override + public void onSuccess(@Nullable Bundle data) { + param.setSwitchStatus(!status); + } + + @Override + public void onResponseFailure(@NonNull Exception exception) { + Toast.makeText(context, R.string.error_param_update, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNetworkFailure(@NonNull Exception exception) { + Toast.makeText(context, R.string.error_param_update, Toast.LENGTH_SHORT).show(); + } + }); + } + break; + } + } + } + break; + } } - }); + break; + + default: + JsonObject jsonParam = new JsonObject(); + JsonObject body = new JsonObject(); + jsonParam.addProperty(param.getName(), !status); + body.add(device.getDeviceName(), jsonParam); + + networkApiManager.updateParamValue(device.getNodeId(), body, new ApiResponseListener() { + + @Override + public void onSuccess(Bundle data) { + param.setSwitchStatus(!status); + } + + @Override + public void onResponseFailure(Exception exception) { + Toast.makeText(context, R.string.error_param_update, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onNetworkFailure(Exception exception) { + Toast.makeText(context, R.string.error_param_update, Toast.LENGTH_SHORT).show(); + } + }); } } }); @@ -371,7 +434,8 @@ public void onNetworkFailure(Exception exception) { deviceVh.tvStringValue.setVisibility(View.GONE); } - if (node != null && !node.isOnline() && !isMatterDeviceOnline) { + if (node != null && !node.isOnline() && nodeStatus != AppConstants.NODE_STATUS_MATTER_LOCAL + && nodeStatus != AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE) { deviceVh.itemView.setAlpha(0.8f); deviceVh.ivDeviceStatus.setImageResource(R.drawable.ic_output_disable); @@ -427,27 +491,40 @@ public void onNetworkFailure(Exception exception) { deviceVh.llOffline.setVisibility(View.INVISIBLE); } - if (!TextUtils.isEmpty(matterNodeId) && espApp.availableMatterDevices.contains(matterNodeId)) { - - deviceVh.llOffline.setVisibility(View.VISIBLE); - deviceVh.ivOffline.setVisibility(View.GONE); - deviceVh.tvOffline.setText(R.string.status_local); - deviceVh.tvOffline.setTextColor(context.getColor(R.color.colorPrimaryDark)); + switch (nodeStatus) { + case AppConstants.NODE_STATUS_MATTER_LOCAL: + if (!TextUtils.isEmpty(matterNodeId)) { + deviceVh.llOffline.setVisibility(View.VISIBLE); + deviceVh.ivOffline.setVisibility(View.GONE); + deviceVh.tvOffline.setText(R.string.status_local); + deviceVh.tvOffline.setTextColor(context.getColor(R.color.colorPrimaryDark)); + } + break; - } else if (espApp.localDeviceMap.containsKey(nodeId)) { + case AppConstants.NODE_STATUS_LOCAL: + deviceVh.llOffline.setVisibility(View.VISIBLE); - deviceVh.llOffline.setVisibility(View.VISIBLE); + EspLocalDevice localDevice = espApp.localDeviceMap.get(nodeId); + if (localDevice.getSecurityType() == 1 || localDevice.getSecurityType() == 2) { + deviceVh.ivSecureLocal.setVisibility(View.VISIBLE); + deviceVh.ivOffline.setVisibility(View.INVISIBLE); + } else { + deviceVh.ivSecureLocal.setVisibility(View.INVISIBLE); + deviceVh.ivOffline.setVisibility(View.GONE); + } + deviceVh.tvOffline.setText(R.string.local_device_text); + deviceVh.tvOffline.setTextColor(context.getColor(R.color.colorPrimaryDark)); + break; - EspLocalDevice localDevice = espApp.localDeviceMap.get(nodeId); - if (localDevice.getSecurityType() == 1 || localDevice.getSecurityType() == 2) { - deviceVh.ivSecureLocal.setVisibility(View.VISIBLE); - deviceVh.ivOffline.setVisibility(View.INVISIBLE); - } else { - deviceVh.ivSecureLocal.setVisibility(View.INVISIBLE); + case AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE: + deviceVh.llOffline.setVisibility(View.VISIBLE); deviceVh.ivOffline.setVisibility(View.GONE); - } - deviceVh.tvOffline.setText(R.string.local_device_text); - deviceVh.tvOffline.setTextColor(context.getColor(R.color.colorPrimaryDark)); + deviceVh.tvOffline.setText(R.string.status_remote); + deviceVh.tvOffline.setTextColor(context.getColor(R.color.colorPrimaryDark)); + break; + + default: + break; } // implement setOnClickListener event on item view. diff --git a/app/src/main/java/com/espressif/ui/adapters/ParamAdapter.java b/app/src/main/java/com/espressif/ui/adapters/ParamAdapter.java index 56625c0..2c5a7f2 100644 --- a/app/src/main/java/com/espressif/ui/adapters/ParamAdapter.java +++ b/app/src/main/java/com/espressif/ui/adapters/ParamAdapter.java @@ -38,6 +38,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SwitchCompat; import androidx.core.widget.ContentLoadingProgressBar; @@ -46,20 +47,24 @@ import com.aar.tapholdupbutton.TapHoldUpButton; import com.espressif.AppConstants; +import com.espressif.ESPControllerAPIKeys; import com.espressif.EspApplication; import com.espressif.cloudapi.ApiResponseListener; import com.espressif.matter.ColorControlClusterHelper; import com.espressif.matter.LevelControlClusterHelper; import com.espressif.matter.OnOffClusterHelper; +import com.espressif.matter.RemoteControlApiHelper; import com.espressif.rainmaker.BuildConfig; import com.espressif.rainmaker.R; import com.espressif.ui.activities.EspDeviceActivity; import com.espressif.ui.activities.TimeSeriesActivity; import com.espressif.ui.models.Device; -import com.espressif.ui.models.Group; +import com.espressif.ui.models.EspNode; import com.espressif.ui.models.Param; +import com.espressif.ui.models.Service; import com.espressif.ui.widgets.EspDropDown; import com.espressif.ui.widgets.PaletteBar; +import com.espressif.utils.NodeUtils; import com.google.gson.JsonObject; import com.larswerkman.holocolorpicker.ColorPicker; import com.warkiz.tickseekbar.OnSeekChangeListener; @@ -70,9 +75,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import kotlin.Unit; public class ParamAdapter extends RecyclerView.Adapter { @@ -91,6 +93,8 @@ public class ParamAdapter extends RecyclerView.Adapter private String matterNodeId; private int hueColorValue; + private EspApplication espApp; + private int nodeStatus = AppConstants.NODE_STATUS_OFFLINE; public ParamAdapter(Activity context, Device device, ArrayList paramList) { this.context = context; @@ -99,7 +103,8 @@ public ParamAdapter(Activity context, Device device, ArrayList paramList) this.deviceName = device.getDeviceName(); deviceParamUpdates = new DeviceParamUpdates(context, nodeId, deviceName); - EspApplication espApp = (EspApplication) context.getApplicationContext(); + espApp = (EspApplication) context.getApplicationContext(); + nodeStatus = espApp.nodeMap.get(nodeId).getNodeStatus(); if (espApp.matterRmNodeIdMap.containsKey(nodeId)) { matterNodeId = espApp.matterRmNodeIdMap.get(nodeId); Log.d(TAG, "Found Matter Node Id : " + matterNodeId); @@ -593,90 +598,155 @@ private void displaySlider(final ParamViewHolder paramViewHolder, final Param pa @Override public void onSeeking(SeekParams seekParams) { - boolean isMatterDeviceOnline = false; - if (!TextUtils.isEmpty(matterNodeId) && espApp.chipClientMap.containsKey(matterNodeId)) { - isMatterDeviceOnline = true; - } + switch (nodeStatus) { - if (!isMatterDeviceOnline) { - if (seekParams.fromUser) { - ((EspDeviceActivity) context).setIsUpdateView(false); - if (BuildConfig.isContinuousUpdateEnable) { - deviceParamUpdates.processSliderChange(param.getName(), seekParams.progress); + case AppConstants.NODE_STATUS_LOCAL: + case AppConstants.NODE_STATUS_ONLINE: + + if (seekParams.fromUser) { + ((EspDeviceActivity) context).setIsUpdateView(false); + if (BuildConfig.isContinuousUpdateEnable) { + deviceParamUpdates.processSliderChange(param.getName(), seekParams.progress); + } } - } + break; + + case AppConstants.NODE_STATUS_OFFLINE: + case AppConstants.NODE_STATUS_MATTER_LOCAL: + case AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE: + default: + break; } } @Override public void onStartTrackingTouch(TickSeekBar seekBar) { - boolean isMatterDeviceOnline = false; - if (!TextUtils.isEmpty(matterNodeId) && espApp.chipClientMap.containsKey(matterNodeId)) { - isMatterDeviceOnline = true; - } + switch (nodeStatus) { - if (!isMatterDeviceOnline) { - ((EspDeviceActivity) context).setIsUpdateView(false); - if (!BuildConfig.isContinuousUpdateEnable) { - ((EspDeviceActivity) context).stopUpdateValueTask(); - } + case AppConstants.NODE_STATUS_LOCAL: + case AppConstants.NODE_STATUS_ONLINE: + + ((EspDeviceActivity) context).setIsUpdateView(false); + if (!BuildConfig.isContinuousUpdateEnable) { + ((EspDeviceActivity) context).stopUpdateValueTask(); + } + break; + + case AppConstants.NODE_STATUS_OFFLINE: + case AppConstants.NODE_STATUS_MATTER_LOCAL: + case AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE: + default: + break; } } @Override public void onStopTrackingTouch(TickSeekBar seekBar) { - boolean isMatterDeviceOnline = false; String paramType = param.getParamType(); - if (AppConstants.PARAM_TYPE_BRIGHTNESS.equals(paramType) - || AppConstants.PARAM_TYPE_SATURATION.equals(paramType)) { + int lastProgressValue = seekBar.getProgress(); - if (!TextUtils.isEmpty(matterNodeId) && espApp.chipClientMap.containsKey(matterNodeId)) { - isMatterDeviceOnline = true; - } - } - - if (isMatterDeviceOnline) { + switch (nodeStatus) { - int lastProgressValue = seekBar.getProgress(); - lastProgressValue = (int) (lastProgressValue * 2.55f); + case AppConstants.NODE_STATUS_MATTER_LOCAL: { + lastProgressValue = (int) (lastProgressValue * 2.55f); + BigInteger id = new BigInteger(matterNodeId, 16); + long deviceId = id.longValue(); - BigInteger id = new BigInteger(matterNodeId, 16); - long deviceId = id.longValue(); + if (lastProgressValue == 255) { + lastProgressValue = 254; + } - if (lastProgressValue == 255) { - lastProgressValue = 254; - } + if (AppConstants.PARAM_TYPE_BRIGHTNESS.equals(paramType) + || AppConstants.PARAM_TYPE_SATURATION.equals(paramType)) { - if (AppConstants.PARAM_TYPE_BRIGHTNESS.equals(paramType)) { - LevelControlClusterHelper espClusterHelper = new LevelControlClusterHelper(espApp.chipClientMap.get(matterNodeId)); - espClusterHelper.setLevelAsync(deviceId, AppConstants.ENDPOINT_1, lastProgressValue); - } else if (AppConstants.PARAM_TYPE_SATURATION.equals(paramType)) { - ColorControlClusterHelper espClusterHelper = new ColorControlClusterHelper(espApp.chipClientMap.get(matterNodeId)); - espClusterHelper.setSaturationValueAsync(deviceId, AppConstants.ENDPOINT_1, lastProgressValue); + if (!TextUtils.isEmpty(matterNodeId) && espApp.chipClientMap.containsKey(matterNodeId)) { + if (AppConstants.PARAM_TYPE_BRIGHTNESS.equals(paramType)) { + LevelControlClusterHelper espClusterHelper = new LevelControlClusterHelper(espApp.chipClientMap.get(matterNodeId)); + espClusterHelper.setLevelAsync(deviceId, AppConstants.ENDPOINT_1, lastProgressValue); + } else if (AppConstants.PARAM_TYPE_SATURATION.equals(paramType)) { + ColorControlClusterHelper espClusterHelper = new ColorControlClusterHelper(espApp.chipClientMap.get(matterNodeId)); + espClusterHelper.setSaturationValueAsync(deviceId, AppConstants.ENDPOINT_1, lastProgressValue); + } + } + } } + break; - } else { - ((EspDeviceActivity) context).setIsUpdateView(true); - int lastProgressValue = seekBar.getProgress(); + case AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE: { - deviceParamUpdates.clearQueueAndSendLastValue(param.getName(), lastProgressValue, new ApiResponseListener() { - @Override - public void onSuccess(Bundle data) { - ((EspDeviceActivity) context).startUpdateValueTask(); + lastProgressValue = (int) (lastProgressValue * 2.55f); + if (lastProgressValue == 255) { + lastProgressValue = 254; } - @Override - public void onResponseFailure(Exception exception) { - ((EspDeviceActivity) context).startUpdateValueTask(); - } + RemoteControlApiHelper apiHelper = new RemoteControlApiHelper(espApp); - @Override - public void onNetworkFailure(Exception exception) { - ((EspDeviceActivity) context).startUpdateValueTask(); + for (Map.Entry> entry : espApp.controllerDevices.entrySet()) { + + HashMap controllerDevices = entry.getValue(); + + if (controllerDevices.containsKey(matterNodeId)) { + + EspNode controllerNode = espApp.nodeMap.get(entry.getKey()); + Service controllerService = NodeUtils.Companion.getService(controllerNode, AppConstants.SERVICE_TYPE_MATTER_CONTROLLER); + + if (controllerService != null) { + + for (Param deviceParam : controllerService.getParams()) { + + if (AppConstants.PARAM_TYPE_MATTER_DEVICES.equals(deviceParam.getParamType())) { + + apiHelper.callBrightnessAPI(entry.getKey(), matterNodeId, deviceParam.getName(), + controllerService.getName(), ESPControllerAPIKeys.ENDPOINT_ID_1, lastProgressValue, new ApiResponseListener() { + @Override + public void onSuccess(@Nullable Bundle data) { + } + + @Override + public void onResponseFailure(@NonNull Exception exception) { + } + + @Override + public void onNetworkFailure(@NonNull Exception exception) { + } + }); + + break; + } + } + } + break; + } } - }); + } + break; + + case AppConstants.NODE_STATUS_LOCAL: + case AppConstants.NODE_STATUS_ONLINE: + ((EspDeviceActivity) context).setIsUpdateView(true); + + deviceParamUpdates.clearQueueAndSendLastValue(param.getName(), lastProgressValue, new ApiResponseListener() { + @Override + public void onSuccess(Bundle data) { + ((EspDeviceActivity) context).startUpdateValueTask(); + } + + @Override + public void onResponseFailure(Exception exception) { + ((EspDeviceActivity) context).startUpdateValueTask(); + } + + @Override + public void onNetworkFailure(Exception exception) { + ((EspDeviceActivity) context).startUpdateValueTask(); + } + }); + + case AppConstants.NODE_STATUS_OFFLINE: + default: + break; } } }); @@ -813,58 +883,115 @@ private void displayToggle(final ParamViewHolder paramViewHolder, final Param pa @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - EspApplication espApp = (EspApplication) context.getApplicationContext(); + switch (nodeStatus) { + case AppConstants.NODE_STATUS_MATTER_LOCAL: + if (!TextUtils.isEmpty(matterNodeId) && espApp.chipClientMap.containsKey(matterNodeId)) { + BigInteger id = new BigInteger(matterNodeId, 16); + long deviceId = id.longValue(); + OnOffClusterHelper espClusterHelper = new OnOffClusterHelper(espApp.chipClientMap.get(matterNodeId)); + espClusterHelper.setOnOffDeviceStateOnOffClusterAsync(deviceId, isChecked, AppConstants.ENDPOINT_1); + param.setSwitchStatus(!isChecked); + if (isChecked) { + paramViewHolder.tvSwitchStatus.setText(R.string.text_on); + } else { + paramViewHolder.tvSwitchStatus.setText(R.string.text_off); + } + } + break; - boolean isMatterDeviceOnline = false; - if (!TextUtils.isEmpty(matterNodeId) && espApp.chipClientMap.containsKey(matterNodeId)) { - isMatterDeviceOnline = true; - } + case AppConstants.NODE_STATUS_REMOTELY_CONTROLLABLE: + RemoteControlApiHelper apiHelper = new RemoteControlApiHelper(espApp); - if (isMatterDeviceOnline) { - BigInteger id = new BigInteger(matterNodeId, 16); - long deviceId = id.longValue(); - OnOffClusterHelper espClusterHelper = new OnOffClusterHelper(espApp.chipClientMap.get(matterNodeId)); - espClusterHelper.setOnOffDeviceStateOnOffClusterAsync(deviceId, isChecked, AppConstants.ENDPOINT_1); + for (Map.Entry> entry : espApp.controllerDevices.entrySet()) { - if (isChecked) { - paramViewHolder.tvSwitchStatus.setText(R.string.text_on); - } else { - paramViewHolder.tvSwitchStatus.setText(R.string.text_off); - } - } else { - ((EspDeviceActivity) context).stopUpdateValueTask(); + HashMap controllerDevices = entry.getValue(); - if (isChecked) { + if (controllerDevices.containsKey(matterNodeId)) { - paramViewHolder.tvSwitchStatus.setText(R.string.text_on); + EspNode espNode = espApp.nodeMap.get(entry.getKey()); + Service controllerService = NodeUtils.Companion.getService(espNode, AppConstants.SERVICE_TYPE_MATTER_CONTROLLER); - } else { - paramViewHolder.tvSwitchStatus.setText(R.string.text_off); - } + if (controllerService != null) { + for (Param deviceParam : controllerService.getParams()) { + if (AppConstants.PARAM_TYPE_MATTER_DEVICES.equals(deviceParam.getParamType())) { - JsonObject jsonParam = new JsonObject(); - JsonObject body = new JsonObject(); + if (isChecked) { + apiHelper.callOnAPI(entry.getKey(), matterNodeId, deviceParam.getName(), controllerService.getName(), ESPControllerAPIKeys.ENDPOINT_ID_1, new ApiResponseListener() { + @Override + public void onSuccess(@Nullable Bundle data) { + } - jsonParam.addProperty(param.getName(), isChecked); - body.add(deviceName, jsonParam); + @Override + public void onResponseFailure(@NonNull Exception exception) { + } - deviceParamUpdates.addParamUpdateRequest(body, new ApiResponseListener() { + @Override + public void onNetworkFailure(@NonNull Exception exception) { + } + }); + } else { + apiHelper.callOffAPI(entry.getKey(), matterNodeId, deviceParam.getName(), controllerService.getName(), ESPControllerAPIKeys.ENDPOINT_ID_1, new ApiResponseListener() { + @Override + public void onSuccess(@Nullable Bundle data) { + } - @Override - public void onSuccess(Bundle data) { - ((EspDeviceActivity) context).startUpdateValueTask(); - } + @Override + public void onResponseFailure(@NonNull Exception exception) { + } - @Override - public void onResponseFailure(Exception exception) { - ((EspDeviceActivity) context).startUpdateValueTask(); + @Override + public void onNetworkFailure(@NonNull Exception exception) { + } + }); + } + + break; + } + } + } + break; + } } + break; - @Override - public void onNetworkFailure(Exception exception) { - ((EspDeviceActivity) context).startUpdateValueTask(); + case AppConstants.NODE_STATUS_LOCAL: + case AppConstants.NODE_STATUS_ONLINE: + ((EspDeviceActivity) context).stopUpdateValueTask(); + + if (isChecked) { + paramViewHolder.tvSwitchStatus.setText(R.string.text_on); + } else { + paramViewHolder.tvSwitchStatus.setText(R.string.text_off); } - }); + + JsonObject jsonParam = new JsonObject(); + JsonObject body = new JsonObject(); + + jsonParam.addProperty(param.getName(), isChecked); + body.add(deviceName, jsonParam); + + deviceParamUpdates.addParamUpdateRequest(body, new ApiResponseListener() { + + @Override + public void onSuccess(Bundle data) { + ((EspDeviceActivity) context).startUpdateValueTask(); + } + + @Override + public void onResponseFailure(Exception exception) { + ((EspDeviceActivity) context).startUpdateValueTask(); + } + + @Override + public void onNetworkFailure(Exception exception) { + ((EspDeviceActivity) context).startUpdateValueTask(); + } + }); + break; + + case AppConstants.NODE_STATUS_OFFLINE: + default: + break; } } }); diff --git a/app/src/main/java/com/espressif/ui/models/EspNode.java b/app/src/main/java/com/espressif/ui/models/EspNode.java index 855ce08..e603209 100644 --- a/app/src/main/java/com/espressif/ui/models/EspNode.java +++ b/app/src/main/java/com/espressif/ui/models/EspNode.java @@ -112,6 +112,12 @@ public class EspNode implements Parcelable { @Ignore private NodeMetadata nodeMetadata; + @Ignore + private boolean isController; + + @Ignore + private int nodeStatus = AppConstants.NODE_STATUS_OFFLINE; + public EspNode() { } @@ -146,6 +152,8 @@ public EspNode(EspNode node) { sceneCurrentCnt = node.getSceneCurrentCnt(); matterNodeId = node.getMatterNodeId(); nodeMetadata = node.getNodeMetadata(); + isController = node.isController(); + nodeStatus = node.getNodeStatus(); } public String getNodeId() { @@ -370,6 +378,22 @@ public void setNodeMetadata(NodeMetadata nodeMetadata) { this.nodeMetadata = nodeMetadata; } + public boolean isController() { + return isController; + } + + public void setController(boolean controller) { + isController = controller; + } + + public int getNodeStatus() { + return nodeStatus; + } + + public void setNodeStatus(int nodeStatus) { + this.nodeStatus = nodeStatus; + } + protected EspNode(Parcel in) { nodeId = in.readString(); @@ -399,6 +423,8 @@ protected EspNode(Parcel in) { isMatterNode = in.readByte() != 0; matterNodeId = in.readString(); nodeMetadata = in.readParcelable(NodeMetadata.class.getClassLoader()); + isController = in.readByte() != 0; + nodeStatus = in.readInt(); } public static final Creator CREATOR = new Creator() { @@ -448,5 +474,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeByte((byte) (isMatterNode ? 1 : 0)); dest.writeString(matterNodeId); dest.writeParcelable(nodeMetadata, flags); + dest.writeByte((byte) (isController ? 1 : 0)); + dest.writeInt(nodeStatus); } } diff --git a/app/src/main/java/com/espressif/utils/NodeUtils.kt b/app/src/main/java/com/espressif/utils/NodeUtils.kt new file mode 100644 index 0000000..344c896 --- /dev/null +++ b/app/src/main/java/com/espressif/utils/NodeUtils.kt @@ -0,0 +1,35 @@ +// Copyright 2024 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.espressif.utils + +import android.text.TextUtils +import com.espressif.ui.models.EspNode +import com.espressif.ui.models.Service + +class NodeUtils { + + companion object { + + fun getService(node: EspNode, serviceType: String): Service? { + + for (service in node.services) { + if (!TextUtils.isEmpty(service.type) && service.type.equals(serviceType)) { + return service + } + } + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cecfe7d..e4f2e79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -154,6 +154,7 @@ Reachable on WLAN Max supported count %d reached Invalid + Remote SIGN IN