diff --git a/examples/java-matter-controller/BUILD.gn b/examples/java-matter-controller/BUILD.gn index a8d426cc48f92d..6314b71fc0f960 100644 --- a/examples/java-matter-controller/BUILD.gn +++ b/examples/java-matter-controller/BUILD.gn @@ -51,6 +51,7 @@ java_library("java") { "java/src/com/matter/controller/commands/pairing/PairOnNetworkFabricCommand.java", "java/src/com/matter/controller/commands/pairing/PairOnNetworkInstanceNameCommand.java", "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongCommand.java", + "java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.java", "java/src/com/matter/controller/commands/pairing/PairOnNetworkShortCommand.java", "java/src/com/matter/controller/commands/pairing/PairOnNetworkVendorCommand.java", "java/src/com/matter/controller/commands/pairing/PairingCommand.java", diff --git a/examples/java-matter-controller/java/src/com/matter/controller/Main.kt b/examples/java-matter-controller/java/src/com/matter/controller/Main.kt index 51d68ad5f8902c..34167a469cfc79 100644 --- a/examples/java-matter-controller/java/src/com/matter/controller/Main.kt +++ b/examples/java-matter-controller/java/src/com/matter/controller/Main.kt @@ -57,6 +57,15 @@ private fun getPairingCommands( ) } +private fun getImCommands( + controller: ChipDeviceController, + credentialsIssuer: CredentialsIssuer +): List { + return listOf( + PairOnNetworkLongImWriteCommand(controller, credentialsIssuer), + ) +} + fun main(args: Array) { val controller = ChipDeviceController( ControllerParams.newBuilder() @@ -70,7 +79,7 @@ fun main(args: Array) { commandManager.register("discover", getDiscoveryCommands(controller, credentialsIssuer)) commandManager.register("pairing", getPairingCommands(controller, credentialsIssuer)) - + commandManager.register("im", getImCommands(controller, credentialsIssuer)) try { commandManager.run(args) } catch (e: Exception) { diff --git a/examples/java-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.java b/examples/java-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.java index a5ee564e6bec9b..e501ec1afe81a4 100644 --- a/examples/java-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.java +++ b/examples/java-matter-controller/java/src/com/matter/controller/commands/common/FutureResult.java @@ -56,10 +56,13 @@ public synchronized void waitResult() { } catch (InterruptedException e) { } } - if (!realResult.get().getResult()) { logger.log(Level.INFO, "error: %s", realResult.get().getError()); throw new RuntimeException("received failure test result"); } } + + public synchronized void clear() { + realResult = Optional.empty(); + } } diff --git a/examples/java-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.java b/examples/java-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.java index 4d958c47e2f316..6af420375e2f8c 100644 --- a/examples/java-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.java +++ b/examples/java-matter-controller/java/src/com/matter/controller/commands/common/MatterCommand.java @@ -102,16 +102,20 @@ public void run() throws Exception { protected abstract void runCommand(); - public void setSuccess() { + public final void setSuccess() { mFutureResult.setRealResult(RealResult.Success()); } - public void setFailure(String error) { + public final void setFailure(String error) { mFutureResult.setRealResult(RealResult.Error(error)); } - public void waitCompleteMs(long timeoutMs) { + public final void waitCompleteMs(long timeoutMs) { mFutureResult.setTimeoutMs(timeoutMs); mFutureResult.waitResult(); } + + public final void clear() { + mFutureResult.clear(); + } } diff --git a/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.java b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.java new file mode 100644 index 00000000000000..f63b995610ec38 --- /dev/null +++ b/examples/java-matter-controller/java/src/com/matter/controller/commands/pairing/PairOnNetworkLongImWriteCommand.java @@ -0,0 +1,99 @@ +package com.matter.controller.commands.pairing; + +import chip.devicecontroller.ChipDeviceController; +import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback; +import chip.devicecontroller.WriteAttributesCallback; +import chip.devicecontroller.model.AttributeWriteRequest; +import chip.devicecontroller.model.ChipAttributePath; +import com.matter.controller.commands.common.CredentialsIssuer; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +public final class PairOnNetworkLongImWriteCommand extends PairingCommand { + private static final int MATTER_PORT = 5540; + private long devicePointer; + private static final int CLUSTER_ID_BASIC = 0x0028; + private static final int ATTR_ID_LOCAL_CONFIG_DISABLED = 16; + private static Logger logger = Logger.getLogger(PairOnNetworkLongImWriteCommand.class.getName()); + + private void setDevicePointer(long devicePointer) { + this.devicePointer = devicePointer; + } + + private class InternalWriteAttributesCallback implements WriteAttributesCallback { + @Override + public void onError(@Nullable ChipAttributePath attributePath, Exception e) { + logger.log(Level.INFO, "Write receive onError on "); + if (attributePath != null) { + logger.log(Level.INFO, attributePath.toString()); + } + + setFailure("write failure"); + } + + @Override + public void onResponse(ChipAttributePath attributePath) { + logger.log(Level.INFO, "Write receve OnResponse on "); + if (attributePath != null) { + logger.log(Level.INFO, attributePath.toString()); + } + setSuccess(); + } + } + + private class InternalGetConnectedDeviceCallback implements GetConnectedDeviceCallback { + @Override + public void onDeviceConnected(long devicePointer) { + setDevicePointer(devicePointer); + logger.log(Level.INFO, "onDeviceConnected"); + } + + @Override + public void onConnectionFailure(long nodeId, Exception error) { + logger.log(Level.INFO, "onConnectionFailure"); + } + } + + public PairOnNetworkLongImWriteCommand( + ChipDeviceController controller, CredentialsIssuer credsIssue) { + super( + controller, + "onnetwork-long-im-write", + PairingModeType.ON_NETWORK, + PairingNetworkType.NONE, + credsIssue, + DiscoveryFilterType.LONG_DISCRIMINATOR); + } + + @Override + protected void runCommand() { + // boolean true for tlv + byte[] booleanTLV = {0x09}; + AttributeWriteRequest attribute = + AttributeWriteRequest.newInstance( + /* endpointId= */ 0, CLUSTER_ID_BASIC, ATTR_ID_LOCAL_CONFIG_DISABLED, booleanTLV); + ArrayList attributeList = new ArrayList<>(); + attributeList.add(attribute); + + currentCommissioner() + .pairDeviceWithAddress( + getNodeId(), + getRemoteAddr().getHostAddress(), + MATTER_PORT, + getDiscriminator(), + getSetupPINCode(), + null); + currentCommissioner().setCompletionListener(this); + waitCompleteMs(getTimeoutMillis()); + currentCommissioner() + .getConnectedDevicePointer(getNodeId(), new InternalGetConnectedDeviceCallback()); + clear(); + + currentCommissioner() + .write(new InternalWriteAttributesCallback(), devicePointer, attributeList, 0, 0); + + waitCompleteMs(getTimeoutMillis()); + } +} diff --git a/src/controller/java/AndroidCallbacks-JNI.cpp b/src/controller/java/AndroidCallbacks-JNI.cpp index 63b2a6306b9810..c3f1dbeaee675d 100644 --- a/src/controller/java/AndroidCallbacks-JNI.cpp +++ b/src/controller/java/AndroidCallbacks-JNI.cpp @@ -70,3 +70,18 @@ JNI_METHOD(void, ReportEventCallbackJni, deleteCallback)(JNIEnv * env, jobject s VerifyOrReturn(reportCallback != nullptr, ChipLogError(Controller, "ReportCallback handle is nullptr")); delete reportCallback; } + +JNI_METHOD(jlong, WriteAttributesCallbackJni, newCallback) +(JNIEnv * env, jobject self, jobject writeAttributesCallbackJava) +{ + WriteAttributesCallback * writeAttributesCallback = + chip::Platform::New(self, writeAttributesCallbackJava); + return reinterpret_cast(writeAttributesCallback); +} + +JNI_METHOD(void, WriteAttributesCallbackJni, deleteCallback)(JNIEnv * env, jobject self, jlong callbackHandle) +{ + WriteAttributesCallback * writeAttributesCallback = reinterpret_cast(callbackHandle); + VerifyOrReturn(writeAttributesCallback != nullptr, ChipLogError(Controller, "WriteAttributesCallback handle is nullptr")); + delete writeAttributesCallback; +} diff --git a/src/controller/java/AndroidCallbacks.cpp b/src/controller/java/AndroidCallbacks.cpp index 17635bae335d09..573ed47a4535b9 100644 --- a/src/controller/java/AndroidCallbacks.cpp +++ b/src/controller/java/AndroidCallbacks.cpp @@ -83,6 +83,7 @@ void GetConnectedDeviceCallback::OnDeviceConnectedFn(void * context, Messaging:: OperationalDeviceProxy * device = new OperationalDeviceProxy(&exchangeMgr, sessionHandle); DeviceLayer::StackUnlock unlock; env->CallVoidMethod(javaCallback, successMethod, reinterpret_cast(device)); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } void GetConnectedDeviceCallback::OnDeviceConnectionFailureFn(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) @@ -118,6 +119,7 @@ void GetConnectedDeviceCallback::OnDeviceConnectionFailureFn(void * context, con DeviceLayer::StackUnlock unlock; env->CallVoidMethod(javaCallback, failureMethod, peerId.GetNodeId(), exception); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } ReportCallback::ReportCallback(jobject wrapperCallback, jobject subscriptionEstablishedCallback, jobject reportCallback, @@ -200,6 +202,7 @@ void ReportCallback::OnReportEnd() DeviceLayer::StackUnlock unlock; env->CallVoidMethod(mReportCallbackRef, onReportMethod, mNodeStateObj); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } void ReportCallback::OnAttributeData(const app::ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, @@ -398,7 +401,7 @@ void ReportCallback::OnEventData(const app::EventHeader & aEventHeader, TLV::TLV VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } -CHIP_ERROR ReportCallback::CreateChipAttributePath(const app::ConcreteDataAttributePath & aPath, jobject & outObj) +CHIP_ERROR CreateChipAttributePath(const app::ConcreteDataAttributePath & aPath, jobject & outObj) { JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); CHIP_ERROR err = CHIP_NO_ERROR; @@ -455,7 +458,7 @@ void ReportCallback::OnDone(app::ReadClient *) DeviceLayer::StackUnlock unlock; env->CallVoidMethod(mReportCallbackRef, onDoneMethod); - + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); JniReferences::GetInstance().GetEnvForCurrentThread()->DeleteGlobalRef(mWrapperCallbackRef); } @@ -479,7 +482,7 @@ CHIP_ERROR ReportCallback::OnResubscriptionNeeded(app::ReadClient * apReadClient DeviceLayer::StackUnlock unlock; env->CallVoidMethod(mResubscriptionAttemptCallbackRef, onResubscriptionAttemptMethod, aTerminationCause.AsInteger(), apReadClient->ComputeTimeTillNextSubscription()); - + VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); return CHIP_NO_ERROR; } @@ -512,6 +515,7 @@ void ReportCallback::ReportError(jobject attributePath, jobject eventPath, const DeviceLayer::StackUnlock unlock; env->CallVoidMethod(mReportCallbackRef, onErrorMethod, attributePath, eventPath, exception); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); } ReportEventCallback::ReportEventCallback(jobject wrapperCallback, jobject subscriptionEstablishedCallback, jobject reportCallback, @@ -762,5 +766,111 @@ void ReportEventCallback::ReportError(jobject eventPath, const char * message, C env->CallVoidMethod(mReportCallbackRef, onErrorMethod, eventPath, exception); } +WriteAttributesCallback::WriteAttributesCallback(jobject wrapperCallback, jobject javaCallback) : mChunkedWriteCallback(this) +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); + + mWrapperCallbackRef = env->NewGlobalRef(wrapperCallback); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); + if (mWrapperCallbackRef == nullptr) + { + ChipLogError(Controller, "Could not create global reference for Wrapper WriteAttributesCallback"); + } + mJavaCallbackRef = env->NewGlobalRef(javaCallback); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); + if (mJavaCallbackRef == nullptr) + { + ChipLogError(Controller, "Could not create global reference for Java WriteAttributesCallback"); + } +} + +WriteAttributesCallback::~WriteAttributesCallback() +{ + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); + env->DeleteGlobalRef(mJavaCallbackRef); + if (mWriteClient != nullptr) + { + Platform::Delete(mWriteClient); + } +} + +void WriteAttributesCallback::OnResponse(const app::WriteClient * apWriteClient, const app::ConcreteDataAttributePath & aPath, + app::StatusIB aStatus) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + jobject attributePathObj = nullptr; + err = CreateChipAttributePath(aPath, attributePathObj); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create Java ChipAttributePath: %s", ErrorStr(err))); + + if (aStatus.mStatus != Protocols::InteractionModel::Status::Success) + { + ReportError(attributePathObj, aStatus.mStatus); + return; + } + + jmethodID onResponseMethod; + err = JniReferences::GetInstance().FindMethod(env, mJavaCallbackRef, "onResponse", + "(Lchip/devicecontroller/model/ChipAttributePath;)V", &onResponseMethod); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to find onError method: %s", ErrorStr(err))); + + DeviceLayer::StackUnlock unlock; + env->CallVoidMethod(mJavaCallbackRef, onResponseMethod, attributePathObj); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); +} + +void WriteAttributesCallback::OnError(const app::WriteClient * apWriteClient, CHIP_ERROR aError) +{ + ReportError(nullptr, aError); +} + +void WriteAttributesCallback::OnDone(app::WriteClient *) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + jmethodID onDoneMethod; + err = JniReferences::GetInstance().FindMethod(env, mJavaCallbackRef, "onDone", "()V", &onDoneMethod); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Could not find onDone method")); + + DeviceLayer::StackUnlock unlock; + env->CallVoidMethod(mJavaCallbackRef, onDoneMethod); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); + JniReferences::GetInstance().GetEnvForCurrentThread()->DeleteGlobalRef(mWrapperCallbackRef); +} + +void WriteAttributesCallback::ReportError(jobject attributePath, CHIP_ERROR err) +{ + ReportError(attributePath, ErrorStr(err), err.AsInteger()); +} + +void WriteAttributesCallback::ReportError(jobject attributePath, Protocols::InteractionModel::Status status) +{ + ReportError(attributePath, "IM Status", static_cast>(status)); +} + +void WriteAttributesCallback::ReportError(jobject attributePath, const char * message, ChipError::StorageType errorCode) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + + ChipLogError(Controller, "ReportError is called"); + jthrowable exception; + err = AndroidClusterExceptions::GetInstance().CreateIllegalStateException(env, message, errorCode, exception); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to create IllegalStateException: %s", ErrorStr(err))); + + jmethodID onErrorMethod; + err = JniReferences::GetInstance().FindMethod(env, mJavaCallbackRef, "onError", + "(Lchip/devicecontroller/model/ChipAttributePath;Ljava/lang/Exception;)V", + &onErrorMethod); + VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Unable to find onError method: %s", ErrorStr(err))); + + DeviceLayer::StackUnlock unlock; + env->CallVoidMethod(mJavaCallbackRef, onErrorMethod, attributePath, exception); + VerifyOrReturn(!env->ExceptionCheck(), env->ExceptionDescribe()); +} + } // namespace Controller } // namespace chip diff --git a/src/controller/java/AndroidCallbacks.h b/src/controller/java/AndroidCallbacks.h index cd7fdfd201265b..8d6c44e7c5a55d 100644 --- a/src/controller/java/AndroidCallbacks.h +++ b/src/controller/java/AndroidCallbacks.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,8 @@ namespace chip { namespace Controller { +CHIP_ERROR CreateChipAttributePath(const app::ConcreteDataAttributePath & aPath, jobject & outObj); + // Callback for success and failure cases of GetConnectedDevice(). struct GetConnectedDeviceCallback { @@ -70,8 +73,6 @@ struct ReportCallback : public app::ClusterStateCache::Callback void ReportError(jobject attributePath, jobject eventPath, Protocols::InteractionModel::Status status); void ReportError(jobject attributePath, jobject eventPath, const char * message, ChipError::StorageType errorCode); - CHIP_ERROR CreateChipAttributePath(const app::ConcreteDataAttributePath & aPath, jobject & outObj); - CHIP_ERROR CreateChipEventPath(const app::ConcreteEventPath & aPath, jobject & outObj); void UpdateClusterDataVersion(); @@ -128,5 +129,28 @@ struct ReportEventCallback : public app::ReadClient::Callback jclass mNodeStateCls = nullptr; }; +struct WriteAttributesCallback : public app::WriteClient::Callback +{ + WriteAttributesCallback(jobject wrapperCallback, jobject javaCallback); + ~WriteAttributesCallback(); + app::WriteClient::Callback * GetChunkedWriteCallback() { return &mChunkedWriteCallback; } + + void OnResponse(const app::WriteClient * apWriteClient, const app::ConcreteDataAttributePath & aPath, + app::StatusIB aStatus) override; + /** Report errors back to Java layer. attributePath may be nullptr for general errors. */ + void OnError(const app::WriteClient * apWriteClient, CHIP_ERROR aProtocolError) override; + + void OnDone(app::WriteClient * apWriteClient) override; + + void ReportError(jobject attributePath, CHIP_ERROR err); + void ReportError(jobject attributePath, Protocols::InteractionModel::Status status); + void ReportError(jobject attributePath, const char * message, ChipError::StorageType errorCode); + + app::WriteClient * mWriteClient = nullptr; + app::ChunkedWriteCallback mChunkedWriteCallback; + jobject mWrapperCallbackRef = nullptr; + jobject mJavaCallbackRef = nullptr; +}; + } // namespace Controller } // namespace chip diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index 840d468ba7a597..017696c7f6d95a 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -196,7 +196,10 @@ android_library("java") { "src/chip/devicecontroller/ResubscriptionAttemptCallback.java", "src/chip/devicecontroller/SubscriptionEstablishedCallback.java", "src/chip/devicecontroller/UnpairDeviceCallback.java", + "src/chip/devicecontroller/WriteAttributesCallback.java", + "src/chip/devicecontroller/WriteAttributesCallbackJni.java", "src/chip/devicecontroller/model/AttributeState.java", + "src/chip/devicecontroller/model/AttributeWriteRequest.java", "src/chip/devicecontroller/model/ChipAttributePath.java", "src/chip/devicecontroller/model/ChipEventPath.java", "src/chip/devicecontroller/model/ChipPathId.java", diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 5da3d86ff4be04..1c15204c6f394a 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -1355,6 +1356,131 @@ JNI_METHOD(void, read) callback->mReadClient = readClient; } +JNI_METHOD(void, write) +(JNIEnv * env, jobject self, jlong handle, jlong callbackHandle, jlong devicePtr, jobject attributeList, jint timedRequestTimeoutMs, + jint imTimeoutMs) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + jint listSize = 0; + auto callback = reinterpret_cast(callbackHandle); + app::WriteClient * writeClient; + + ChipLogDetail(Controller, "IM write() called"); + + DeviceProxy * device = reinterpret_cast(devicePtr); + VerifyOrExit(device != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION); + VerifyOrExit(attributeList != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + SuccessOrExit(JniReferences::GetInstance().GetListSize(attributeList, listSize)); + + writeClient = Platform::New(device->GetExchangeManager(), callback->GetChunkedWriteCallback(), + timedRequestTimeoutMs != 0 ? Optional(timedRequestTimeoutMs) + : Optional::Missing()); + + for (uint8_t i = 0; i < listSize; i++) + { + jobject attributeItem = nullptr; + uint32_t endpointId = 0; + uint32_t clusterId = 0; + uint32_t attributeId = 0; + jmethodID getEndpointIdMethod = nullptr; + jmethodID getClusterIdMethod = nullptr; + jmethodID getAttributeIdMethod = nullptr; + jmethodID hasDataVersionMethod = nullptr; + jmethodID getDataVersionMethod = nullptr; + jmethodID getTlvByteArrayMethod = nullptr; + jobject endpointIdObj = nullptr; + jobject clusterIdObj = nullptr; + jobject attributeIdObj = nullptr; + jbyteArray tlvBytesObj = nullptr; + bool hasDataVersion = false; + Optional dataVersion = Optional(); + ; + jbyte * tlvBytesObjBytes = nullptr; + jsize length = 0; + TLV::TLVReader reader; + + SuccessOrExit(JniReferences::GetInstance().GetListItem(attributeList, i, attributeItem)); + SuccessOrExit(JniReferences::GetInstance().FindMethod(env, attributeItem, "getEndpointId", + "()Lchip/devicecontroller/model/ChipPathId;", &getEndpointIdMethod)); + SuccessOrExit(JniReferences::GetInstance().FindMethod(env, attributeItem, "getClusterId", + "()Lchip/devicecontroller/model/ChipPathId;", &getClusterIdMethod)); + SuccessOrExit(JniReferences::GetInstance().FindMethod(env, attributeItem, "getAttributeId", + "()Lchip/devicecontroller/model/ChipPathId;", &getAttributeIdMethod)); + SuccessOrExit(JniReferences::GetInstance().FindMethod(env, attributeItem, "hasDataVersion", "()Z", &hasDataVersionMethod)); + SuccessOrExit(JniReferences::GetInstance().FindMethod(env, attributeItem, "getDataVersion", "()I", &getDataVersionMethod)); + SuccessOrExit( + JniReferences::GetInstance().FindMethod(env, attributeItem, "getTlvByteArray", "()[B", &getTlvByteArrayMethod)); + + endpointIdObj = env->CallObjectMethod(attributeItem, getEndpointIdMethod); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + VerifyOrExit(endpointIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + + clusterIdObj = env->CallObjectMethod(attributeItem, getClusterIdMethod); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + VerifyOrExit(clusterIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + + attributeIdObj = env->CallObjectMethod(attributeItem, getAttributeIdMethod); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + VerifyOrExit(attributeIdObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + + SuccessOrExit(GetChipPathIdValue(endpointIdObj, kInvalidEndpointId, endpointId)); + SuccessOrExit(GetChipPathIdValue(clusterIdObj, kInvalidClusterId, clusterId)); + SuccessOrExit(GetChipPathIdValue(attributeIdObj, kInvalidAttributeId, attributeId)); + + hasDataVersion = static_cast(env->CallBooleanMethod(attributeItem, hasDataVersionMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + if (hasDataVersion) + { + DataVersion dataVersionVal = static_cast(env->CallIntMethod(attributeItem, getDataVersionMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + dataVersion.SetValue(dataVersionVal); + } + + tlvBytesObj = static_cast(env->CallObjectMethod(attributeItem, getTlvByteArrayMethod)); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + VerifyOrExit(tlvBytesObj != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + + tlvBytesObjBytes = env->GetByteArrayElements(tlvBytesObj, nullptr); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + length = env->GetArrayLength(tlvBytesObj); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + + reader.Init(reinterpret_cast(tlvBytesObjBytes), static_cast(length)); + reader.Next(); + SuccessOrExit( + err = writeClient->PutPreencodedAttribute( + chip::app::ConcreteDataAttributePath(static_cast(endpointId), static_cast(clusterId), + static_cast(attributeId), dataVersion), + reader)); + } + + err = writeClient->SendWriteRequest(device->GetSecureSession().Value(), + imTimeoutMs != 0 ? System::Clock::Milliseconds32(imTimeoutMs) : System::Clock::kZero); + if (err != CHIP_NO_ERROR) + { + callback->OnError(writeClient, err); + delete writeClient; + delete callback; + return; + } + + callback->mWriteClient = writeClient; + +exit: + if (err == CHIP_JNI_ERROR_EXCEPTION_THROWN) + { + ChipLogError(DeviceLayer, "Java exception in IM Write JNI"); + env->ExceptionDescribe(); + env->ExceptionClear(); + } + if (err != CHIP_NO_ERROR) + { + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } +} + /** * Takes objects in attributePathList, converts them to app:AttributePathParams, and appends them to outAttributePathParamsList. */ @@ -1520,13 +1646,16 @@ CHIP_ERROR IsWildcardChipPathId(jobject chipPathId, bool & isWildcard) env, chipPathId, "getType", "()Lchip/devicecontroller/model/ChipPathId$IdType;", &getTypeMethod)); jobject idType = env->CallObjectMethod(chipPathId, getTypeMethod); + VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); VerifyOrReturnError(idType != nullptr, CHIP_JNI_ERROR_NULL_OBJECT); jmethodID nameMethod = nullptr; ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, idType, "name", "()Ljava/lang/String;", &nameMethod)); jstring typeNameString = static_cast(env->CallObjectMethod(idType, nameMethod)); + VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); VerifyOrReturnError(typeNameString != nullptr, CHIP_JNI_ERROR_NULL_OBJECT); + JniUtfString typeNameJniString(env, typeNameString); isWildcard = strncmp(typeNameJniString.c_str(), "WILDCARD", 8) == 0; diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index 8775e687f3f0f5..fbe2f172d8786c 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -20,6 +20,7 @@ import android.bluetooth.BluetoothGatt; import android.util.Log; import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback; +import chip.devicecontroller.model.AttributeWriteRequest; import chip.devicecontroller.model.ChipAttributePath; import chip.devicecontroller.model.ChipEventPath; import java.util.ArrayList; @@ -599,6 +600,32 @@ public void readPath( isFabricFiltered); } + /** + * @brief Write a list of attributes into target device + * @param WriteAttributesCallback Callback when a write response has been received and processed + * for the given path. + * @param devicePtr connected device pointer + * @param attributeList a list of attributes + * @param timedRequestTimeoutMs this is timed request if this value is larger than 0 + * @param imTimeoutMs im interaction time out value, it would override the default value in c++ im + * layer if this value is non-zero. + */ + public void write( + WriteAttributesCallback callback, + long devicePtr, + List attributeList, + int timedRequestTimeoutMs, + int imTimeoutMs) { + WriteAttributesCallbackJni jniCallback = new WriteAttributesCallbackJni(callback); + write( + deviceControllerPtr, + jniCallback.getCallbackHandle(), + devicePtr, + attributeList, + timedRequestTimeoutMs, + imTimeoutMs); + } + /** * Converts a given X.509v3 certificate into a Matter certificate. * @@ -646,6 +673,14 @@ private native void read( List eventPaths, boolean isFabricFiltered); + private native void write( + long deviceControllerPtr, + long callbackHandle, + long devicePtr, + List attributeList, + int timedRequestTimeoutMs, + int imTimeoutMs); + private native long newDeviceController(ControllerParams params); private native void setDeviceAttestationDelegate( diff --git a/src/controller/java/src/chip/devicecontroller/WriteAttributesCallback.java b/src/controller/java/src/chip/devicecontroller/WriteAttributesCallback.java new file mode 100644 index 00000000000000..4e469ab0e58245 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/WriteAttributesCallback.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 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; + +import chip.devicecontroller.model.ChipAttributePath; +import javax.annotation.Nullable; + +/** An interface for receiving write response. */ +public interface WriteAttributesCallback { + + /** + * OnError will be called when an error occurs after failing to write + * + * @param attributePath The attribute path field + * @param Exception The IllegalStateException which encapsulated the error message, the possible + * chip error could be CHIP_ERROR_TIMEOUT: A response was not received within the expected + * response timeout. - CHIP_ERROR_*TLV*: A malformed, non-compliant response was received from + * the server. - CHIP_ERROR encapsulating the converted error from the StatusIB: If we got a + * non-path-specific status response from the server. CHIP_ERROR*: All other cases. + */ + void onError(@Nullable ChipAttributePath attributePath, Exception e); + + /** + * OnResponse will be called when a write response has been received and processed for the given + * path. + * + * @param attributePath The attribute path field in write response. + */ + void onResponse(ChipAttributePath attributePath); + + default void onDone() {} +} diff --git a/src/controller/java/src/chip/devicecontroller/WriteAttributesCallbackJni.java b/src/controller/java/src/chip/devicecontroller/WriteAttributesCallbackJni.java new file mode 100644 index 00000000000000..fb7d7136ed0572 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/WriteAttributesCallbackJni.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 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; + +/** JNI wrapper callback class for {@link WriteAttributesCallback}. */ +public class WriteAttributesCallbackJni { + private final WriteAttributesCallback wrappedWriteAttributesCallback; + private long callbackHandle; + + public WriteAttributesCallbackJni(WriteAttributesCallback wrappedWriteAttributesCallback) { + this.wrappedWriteAttributesCallback = wrappedWriteAttributesCallback; + this.callbackHandle = newCallback(wrappedWriteAttributesCallback); + } + + long getCallbackHandle() { + return callbackHandle; + } + + private native long newCallback(WriteAttributesCallback wrappedCallback); + + private native void deleteCallback(long callbackHandle); + + // TODO(#8578): Replace finalizer with PhantomReference. + @SuppressWarnings("deprecation") + protected void finalize() throws Throwable { + super.finalize(); + + if (callbackHandle != 0) { + deleteCallback(callbackHandle); + callbackHandle = 0; + } + } +} diff --git a/src/controller/java/src/chip/devicecontroller/model/AttributeWriteRequest.java b/src/controller/java/src/chip/devicecontroller/model/AttributeWriteRequest.java new file mode 100644 index 00000000000000..b479076e13e40a --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/model/AttributeWriteRequest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2023 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 java.util.Locale; +import java.util.Objects; +import java.util.Optional; + +/** An attribute write request that should be used for interaction model write interaction. */ +public final class AttributeWriteRequest { + private final ChipPathId endpointId, clusterId, attributeId; + private final Optional dataVersion; + private final byte[] tlv; + + private AttributeWriteRequest( + ChipPathId endpointId, + ChipPathId clusterId, + ChipPathId attributeId, + byte[] tlv, + Optional dataVersion) { + this.endpointId = endpointId; + this.clusterId = clusterId; + this.attributeId = attributeId; + this.tlv = tlv.clone(); + this.dataVersion = dataVersion; + } + + public ChipPathId getEndpointId() { + return endpointId; + } + + public ChipPathId getClusterId() { + return clusterId; + } + + public ChipPathId getAttributeId() { + return attributeId; + } + + public int getDataVersion() { + return dataVersion.orElse(0); + } + + public boolean hasDataVersion() { + return dataVersion.isPresent(); + } + + public byte[] getTlvByteArray() { + return tlv.clone(); + } + + // check whether the current AttributeWriteRequest has same path as others. + @Override + public boolean equals(Object object) { + if (object instanceof AttributeWriteRequest) { + AttributeWriteRequest that = (AttributeWriteRequest) object; + return Objects.equals(this.endpointId, that.endpointId) + && Objects.equals(this.clusterId, that.clusterId) + && Objects.equals(this.attributeId, that.attributeId); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(endpointId, clusterId, attributeId); + } + + @Override + public String toString() { + return String.format( + Locale.ENGLISH, + "Endpoint %s, cluster %s, attribute %s", + endpointId, + clusterId, + attributeId); + } + + public static AttributeWriteRequest newInstance( + ChipPathId endpointId, ChipPathId clusterId, ChipPathId attributeId, byte[] tlv) { + return new AttributeWriteRequest(endpointId, clusterId, attributeId, tlv, Optional.empty()); + } + + public static AttributeWriteRequest newInstance( + ChipPathId endpointId, + ChipPathId clusterId, + ChipPathId attributeId, + byte[] tlv, + Optional dataVersion) { + return new AttributeWriteRequest(endpointId, clusterId, attributeId, tlv, dataVersion); + } + + /** Create a new {@link AttributeWriteRequest} with only concrete ids. */ + public static AttributeWriteRequest newInstance( + long endpointId, long clusterId, long attributeId, byte[] tlv) { + return new AttributeWriteRequest( + ChipPathId.forId(endpointId), + ChipPathId.forId(clusterId), + ChipPathId.forId(attributeId), + tlv, + Optional.empty()); + } + + public static AttributeWriteRequest newInstance( + long endpointId, + long clusterId, + long attributeId, + byte[] tlv, + Optional dataVersion) { + return new AttributeWriteRequest( + ChipPathId.forId(endpointId), + ChipPathId.forId(clusterId), + ChipPathId.forId(attributeId), + tlv, + dataVersion); + } +}