diff --git a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt index e7a7c867276ba7..f79eb8a8466566 100644 --- a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt +++ b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/clusterclient/WildcardFragment.kt @@ -8,7 +8,6 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.EditText -import androidx.appcompat.widget.MenuItemHoverListener import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import chip.devicecontroller.ChipDeviceController @@ -16,6 +15,7 @@ import chip.devicecontroller.ReportCallback import chip.devicecontroller.SubscriptionEstablishedCallback import chip.devicecontroller.model.ChipAttributePath import chip.devicecontroller.model.ChipPathId +import chip.devicecontroller.model.NodeState import com.google.chip.chiptool.ChipClient import com.google.chip.chiptool.R import kotlinx.android.synthetic.main.wildcard_fragment.attributeIdEd @@ -37,17 +37,13 @@ class WildcardFragment : Fragment() { private val reportCallback = object : ReportCallback { override fun onError(attributePath: ChipAttributePath, ex: Exception) { - Log.i(TAG, "Report error for $attributePath: $ex") + Log.e(TAG, "Report error for $attributePath: $ex") } - override fun onReport(values: Map) { - Log.i(TAG, "Received report with ${values.size} values") - val builder = StringBuilder() - values.forEach { builder.append("${it.key}: ${it.value}\n") } - val output = builder.toString() - - Log.i(TAG, output) - requireActivity().runOnUiThread { outputTv.text = output } + override fun onReport(nodeData: NodeState) { + Log.i(TAG, "Received wildcard report") + Log.i(TAG, nodeData.toString()) + requireActivity().runOnUiThread { outputTv.text = nodeData.toString() } } } diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp index faa64789399dc8..6dc10ee617df69 100644 --- a/src/controller/java/AndroidCallbacks.cpp +++ b/src/controller/java/AndroidCallbacks.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace chip { @@ -155,7 +156,18 @@ ReportCallback::~ReportCallback() void ReportCallback::OnReportBegin() { - mReports.clear(); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + CHIP_ERROR err = CHIP_NO_ERROR; + + err = JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/model/NodeState", mNodeStateCls); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not get NodeState class")); + jmethodID nodeStateCtor = env->GetMethodID(mNodeStateCls, "", "(Ljava/util/Map;)V"); + VerifyOrReturn(nodeStateCtor != nullptr, ChipLogError(Controller, "Could not find NodeState constructor")); + + jobject map = nullptr; + err = JniReferences::GetInstance().CreateHashMap(map); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not create HashMap")); + mNodeStateObj = env->NewObject(mNodeStateCls, nodeStateCtor, map); } void ReportCallback::OnReportEnd() @@ -164,19 +176,13 @@ void ReportCallback::OnReportEnd() CHIP_ERROR err = CHIP_NO_ERROR; JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - jobject map; - JniReferences::GetInstance().CreateHashMap(map); - for (std::pair reportPair : mReports) - { - JniReferences::GetInstance().PutInMap(map, reportPair.first, reportPair.second); - } - jmethodID onReportMethod; - err = JniReferences::GetInstance().FindMethod(env, mReportCallbackRef, "onReport", "(Ljava/util/Map;)V", &onReportMethod); + err = JniReferences::GetInstance().FindMethod(env, mReportCallbackRef, "onReport", "(Lchip/devicecontroller/model/NodeState;)V", + &onReportMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onReport method")); DeviceLayer::StackUnlock unlock; - env->CallVoidMethod(mReportCallbackRef, onReportMethod, map); + env->CallVoidMethod(mReportCallbackRef, onReportMethod, mNodeStateObj); } void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, @@ -206,14 +212,50 @@ void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPat return; } - jobject value = DecodeAttributeValue(aPath, *apData, &err); + TLV::TLVReader readerForJavaObject; + TLV::TLVReader readerForJavaTLV; + readerForJavaObject.Init(*apData); + readerForJavaTLV.Init(*apData); + + jobject value = DecodeAttributeValue(aPath, readerForJavaObject, &err); // If we don't know this attribute, just skip it. VerifyOrReturn(err != CHIP_ERROR_IM_MALFORMED_ATTRIBUTE_PATH); VerifyOrReturn(err == CHIP_NO_ERROR, ReportError(attributePathObj, err)); VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe(), ReportError(attributePathObj, CHIP_JNI_ERROR_EXCEPTION_THROWN)); - mReports.push_back(std::make_pair(attributePathObj, value)); + // Create TLV byte array to pass to Java layer + size_t bufferLen = readerForJavaTLV.GetRemainingLength() + readerForJavaTLV.GetLengthRead(); + std::unique_ptr buffer = std::unique_ptr(new uint8_t[bufferLen]); + uint32_t size = 0; + // The TLVReader's read head is not pointing to the first element in the container, instead of the container itself, use + // a TLVWriter to get a TLV with a normalized TLV buffer (Wrapped with an anonymous tag, no extra "end of container" tag + // at the end.) + TLV::TLVWriter writer; + writer.Init(buffer.get(), bufferLen); + err = writer.CopyElement(TLV::AnonymousTag(), readerForJavaTLV); + VerifyOrReturn(err == CHIP_NO_ERROR, ReportError(attributePathObj, err)); + size = writer.GetLengthWritten(); + chip::ByteArray jniByteArray(env, reinterpret_cast(buffer.get()), size); + + // Create AttributeState object + jclass attributeStateCls; + err = JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/model/AttributeState", attributeStateCls); + VerifyOrReturn(attributeStateCls != nullptr, ChipLogError(Controller, "Could not find AttributeState class")); + chip::JniClass attributeStateJniCls(attributeStateCls); + jmethodID attributeStateCtor = env->GetMethodID(attributeStateCls, "", "(Ljava/lang/Object;[B)V"); + VerifyOrReturn(attributeStateCtor != nullptr, ChipLogError(Controller, "Could not find AttributeState constructor")); + jobject attributeStateObj = env->NewObject(attributeStateCls, attributeStateCtor, value, jniByteArray.jniValue()); + VerifyOrReturn(attributeStateObj != nullptr, ChipLogError(Controller, "Could not create AttributeState object")); + + // Add AttributeState to NodeState + jmethodID addAttributeMethod; + err = JniReferences::GetInstance().FindMethod(env, mNodeStateObj, "addAttribute", + "(IJJLchip/devicecontroller/model/AttributeState;)V", &addAttributeMethod); + VerifyOrReturnError(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find addAttribute method")); + env->CallVoidMethod(mNodeStateObj, addAttributeMethod, static_cast(aPath.mEndpointId), + static_cast(aPath.mClusterId), static_cast(aPath.mAttributeId), attributeStateObj); + VerifyOrReturnError(!env->ExceptionCheck(), env->ExceptionDescribe()); } CHIP_ERROR ReportCallback::CreateChipAttributePath(const app::ConcreteDataAttributePath & aPath, jobject & outObj) diff --git a/src/controller/java/AndroidCallbacks.h b/src/controller/java/AndroidCallbacks.h index 3935afb8cb72bc..5531a776374029 100644 --- a/src/controller/java/AndroidCallbacks.h +++ b/src/controller/java/AndroidCallbacks.h @@ -74,8 +74,9 @@ struct ReportCallback : public app::ReadClient::Callback jobject mWrapperCallbackRef = nullptr; jobject mSubscriptionEstablishedCallbackRef = nullptr; jobject mReportCallbackRef = nullptr; - // List of pairs of Java ChipAttributePath and report value. Not using map because jobjects should not be keys. - std::list> mReports; + // NodeState Java object that will be returned to the application. + jobject mNodeStateObj = nullptr; + jclass mNodeStateCls = nullptr; }; } // namespace Controller diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index 80f9e603057fd2..1a1e5588ea22b2 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -91,8 +91,12 @@ android_library("java") { "src/chip/devicecontroller/ReportCallback.java", "src/chip/devicecontroller/ReportCallbackJni.java", "src/chip/devicecontroller/SubscriptionEstablishedCallback.java", + "src/chip/devicecontroller/model/AttributeState.java", "src/chip/devicecontroller/model/ChipAttributePath.java", "src/chip/devicecontroller/model/ChipPathId.java", + "src/chip/devicecontroller/model/ClusterState.java", + "src/chip/devicecontroller/model/EndpointState.java", + "src/chip/devicecontroller/model/NodeState.java", "zap-generated/chip/devicecontroller/ChipClusters.java", "zap-generated/chip/devicecontroller/ChipStructs.java", "zap-generated/chip/devicecontroller/ClusterInfoMapping.java", diff --git a/src/controller/java/src/chip/devicecontroller/ReportCallback.java b/src/controller/java/src/chip/devicecontroller/ReportCallback.java index 172f259da2303c..8d4859693db871 100644 --- a/src/controller/java/src/chip/devicecontroller/ReportCallback.java +++ b/src/controller/java/src/chip/devicecontroller/ReportCallback.java @@ -18,11 +18,11 @@ package chip.devicecontroller; import chip.devicecontroller.model.ChipAttributePath; -import java.util.Map; +import chip.devicecontroller.model.NodeState; /** An interface for receiving read/subscribe CHIP reports. */ public interface ReportCallback { void onError(ChipAttributePath attributePath, Exception e); - void onReport(Map values); + void onReport(NodeState nodeState); } diff --git a/src/controller/java/src/chip/devicecontroller/model/AttributeState.java b/src/controller/java/src/chip/devicecontroller/model/AttributeState.java new file mode 100644 index 00000000000000..2920d321f38334 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/model/AttributeState.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * 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 chip.devicecontroller.model; + +/** Represents the reported value of an attribute in object form AND TLV. */ +public final class AttributeState { + private Object valueObject; + private byte[] tlv; + + public AttributeState(Object valueObject, byte[] tlv) { + this.valueObject = valueObject; + this.tlv = tlv; + } + + public Object getValue() { + return valueObject; + } + + /** + * Return a byte array containing the TLV for an attribute, wrapped within an anonymous TLV tag. + */ + public byte[] getTlv() { + return tlv; + } +} diff --git a/src/controller/java/src/chip/devicecontroller/model/ChipAttributePath.java b/src/controller/java/src/chip/devicecontroller/model/ChipAttributePath.java index 9a8cd94e90d4ae..31d7a366994c2b 100644 --- a/src/controller/java/src/chip/devicecontroller/model/ChipAttributePath.java +++ b/src/controller/java/src/chip/devicecontroller/model/ChipAttributePath.java @@ -20,7 +20,7 @@ import java.util.Locale; import java.util.Objects; -/** An attribute path that could be used in a request, or received in a report. */ +/** An attribute path that should be used for requests. */ public class ChipAttributePath { private ChipPathId endpointId, clusterId, attributeId; diff --git a/src/controller/java/src/chip/devicecontroller/model/ClusterState.java b/src/controller/java/src/chip/devicecontroller/model/ClusterState.java new file mode 100644 index 00000000000000..ee37b452dfcc55 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/model/ClusterState.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * 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 chip.devicecontroller.model; + +import androidx.annotation.Nullable; +import java.util.Map; + +/** Class for tracking CHIP cluster state in a hierarchical manner. */ +public final class ClusterState { + private Map attributes; + + public ClusterState(Map attributes) { + this.attributes = attributes; + } + + public Map getAttributeStates() { + return attributes; + } + + /** + * Convenience utility for getting an {@code ClusterState}. + * + * @return the {@code ClusterState} for the specified id, or null if not found. + */ + @Nullable + public AttributeState getAttributeState(long attributeId) { + return attributes.get(attributeId); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + attributes.forEach( + (attributeId, attributeState) -> { + builder.append("Attribute "); + builder.append(attributeId); + builder.append(": "); + builder.append( + attributeState.getValue() == null ? "null" : attributeState.getValue().toString()); + builder.append("\n"); + }); + return builder.toString(); + } +} diff --git a/src/controller/java/src/chip/devicecontroller/model/EndpointState.java b/src/controller/java/src/chip/devicecontroller/model/EndpointState.java new file mode 100644 index 00000000000000..fe6958d63cf682 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/model/EndpointState.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * 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 chip.devicecontroller.model; + +import androidx.annotation.Nullable; +import java.util.Map; + +/** Class for tracking CHIP endpoint state in a hierarchical manner. */ +public final class EndpointState { + private Map clusters; + + public EndpointState(Map clusters) { + this.clusters = clusters; + } + + public Map getClusterStates() { + return clusters; + } + + /** + * Convenience utility for getting an {@code ClusterState}. + * + * @return the {@code ClusterState} for the specified id, or null if not found. + */ + @Nullable + public ClusterState getClusterState(long clusterId) { + return clusters.get(clusterId); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + clusters.forEach( + (clusterId, clusterState) -> { + builder.append("Cluster "); + builder.append(clusterId); + builder.append(": {\n"); + builder.append(clusterState.toString()); + builder.append("}\n"); + }); + return builder.toString(); + } +} diff --git a/src/controller/java/src/chip/devicecontroller/model/NodeState.java b/src/controller/java/src/chip/devicecontroller/model/NodeState.java new file mode 100644 index 00000000000000..a4e6585c26e4b2 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/model/NodeState.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * 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 chip.devicecontroller.model; + +import androidx.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +/** Class for tracking CHIP node state in a hierarchical manner. */ +public final class NodeState { + private Map endpoints; + + public NodeState(Map endpoints) { + this.endpoints = endpoints; + } + + public Map getEndpointStates() { + return endpoints; + } + + // Called from native code only, which ignores access modifiers. + private void addAttribute( + int endpointId, long clusterId, long attributeId, AttributeState attributeStateToAdd) { + EndpointState endpointState = getEndpointState(endpointId); + if (endpointState == null) { + endpointState = new EndpointState(new HashMap<>()); + getEndpointStates().put(endpointId, endpointState); + } + + ClusterState clusterState = endpointState.getClusterState(clusterId); + if (clusterState == null) { + clusterState = new ClusterState(new HashMap<>()); + endpointState.getClusterStates().put(clusterId, clusterState); + } + + // This will overwrite previous attributes. + clusterState.getAttributeStates().put(attributeId, attributeStateToAdd); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + endpoints.forEach( + (endpointId, endpointState) -> { + builder.append("Endpoint "); + builder.append(endpointId); + builder.append(": {\n"); + builder.append(endpointState.toString()); + builder.append("}\n"); + }); + return builder.toString(); + } + + /** + * Convenience utility for getting an {@code EndpointState}. + * + * @return the {@code EndpointState} for the specified id, or null if not found. + */ + @Nullable + public EndpointState getEndpointState(int endpointId) { + return endpoints.get(endpointId); + } +}