|
19 | 19 | #import "MTRCluster_internal.h"
|
20 | 20 | #import "zap-generated/MTRCallbackBridge_internal.h"
|
21 | 21 |
|
| 22 | +#include <app/CommandSender.h> |
22 | 23 | #include <app/ReadClient.h>
|
| 24 | +#include <app/data-model/NullObject.h> |
23 | 25 | #include <lib/core/CHIPTLV.h>
|
24 | 26 | #include <lib/core/DataModelTypes.h>
|
25 | 27 | #include <lib/support/CHIPMem.h>
|
@@ -260,4 +262,145 @@ void MTRReadAttribute(MTRReadParams * _Nonnull params,
|
260 | 262 | std::move(*callbackBridge).DispatchAction(device);
|
261 | 263 | }
|
262 | 264 |
|
| 265 | +/** |
| 266 | + * Utility functions base clusters use for doing commands. |
| 267 | + */ |
| 268 | +template <typename InvokeBridgeType, typename ResponseType> class MTRInvokeCallback : public chip::app::CommandSender::Callback { |
| 269 | +public: |
| 270 | + MTRInvokeCallback(InvokeBridgeType * _Nonnull bridge, typename InvokeBridgeType::SuccessCallbackType _Nonnull onResponse, |
| 271 | + MTRErrorCallback _Nonnull onError) |
| 272 | + : mBridge(bridge) |
| 273 | + , mOnResponse(onResponse) |
| 274 | + , mOnError(onError) |
| 275 | + { |
| 276 | + } |
| 277 | + |
| 278 | + ~MTRInvokeCallback() {} |
| 279 | + |
| 280 | + void AdoptCommandSender(chip::Platform::UniquePtr<chip::app::CommandSender> commandSender) |
| 281 | + { |
| 282 | + mCommandSender = std::move(commandSender); |
| 283 | + } |
| 284 | + |
| 285 | +protected: |
| 286 | + // We need to have different OnResponse implementations depending on whether |
| 287 | + // ResponseType is DataModel::NullObjectType or not. Since template class methods |
| 288 | + // can't be partially specialized (either you have to partially specialize |
| 289 | + // the class template, or you have to fully specialize the method), use |
| 290 | + // enable_if to deal with this. |
| 291 | + void OnResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath, |
| 292 | + const chip::app::StatusIB & status, chip::TLV::TLVReader * reader) override |
| 293 | + { |
| 294 | + HandleResponse(commandSender, commandPath, status, reader); |
| 295 | + } |
| 296 | + |
| 297 | + /** |
| 298 | + * Response handler for data responses. |
| 299 | + */ |
| 300 | + template <typename T = ResponseType, std::enable_if_t<!std::is_same<T, chip::app::DataModel::NullObjectType>::value, int> = 0> |
| 301 | + void HandleResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath, |
| 302 | + const chip::app::StatusIB & status, chip::TLV::TLVReader * reader) |
| 303 | + { |
| 304 | + if (mCalledCallback) { |
| 305 | + return; |
| 306 | + } |
| 307 | + mCalledCallback = true; |
| 308 | + |
| 309 | + ResponseType response; |
| 310 | + CHIP_ERROR err = CHIP_NO_ERROR; |
| 311 | + |
| 312 | + // |
| 313 | + // We're expecting response data in this variant of OnResponse. Consequently, reader should always be |
| 314 | + // non-null. If it is, it means we received a success status code instead, which is not what was expected. |
| 315 | + // |
| 316 | + VerifyOrExit(reader != nullptr, err = CHIP_ERROR_SCHEMA_MISMATCH); |
| 317 | + |
| 318 | + // |
| 319 | + // Validate that the data response we received matches what we expect in terms of its cluster and command IDs. |
| 320 | + // |
| 321 | + VerifyOrExit( |
| 322 | + commandPath.mClusterId == ResponseType::GetClusterId() && commandPath.mCommandId == ResponseType::GetCommandId(), |
| 323 | + err = CHIP_ERROR_SCHEMA_MISMATCH); |
| 324 | + |
| 325 | + err = chip::app::DataModel::Decode(*reader, response); |
| 326 | + SuccessOrExit(err); |
| 327 | + |
| 328 | + mOnResponse(mBridge, response); |
| 329 | + |
| 330 | + exit: |
| 331 | + if (err != CHIP_NO_ERROR) { |
| 332 | + mOnError(mBridge, err); |
| 333 | + } |
| 334 | + } |
| 335 | + |
| 336 | + /** |
| 337 | + * Response handler for status responses. |
| 338 | + */ |
| 339 | + template <typename T = ResponseType, std::enable_if_t<std::is_same<T, chip::app::DataModel::NullObjectType>::value, int> = 0> |
| 340 | + void HandleResponse(chip::app::CommandSender * commandSender, const chip::app::ConcreteCommandPath & commandPath, |
| 341 | + const chip::app::StatusIB & status, chip::TLV::TLVReader * reader) |
| 342 | + { |
| 343 | + if (mCalledCallback) { |
| 344 | + return; |
| 345 | + } |
| 346 | + mCalledCallback = true; |
| 347 | + |
| 348 | + // |
| 349 | + // If we got a valid reader, it means we received response data that we were not expecting to receive. |
| 350 | + // |
| 351 | + if (reader != nullptr) { |
| 352 | + mOnError(mBridge, CHIP_ERROR_SCHEMA_MISMATCH); |
| 353 | + return; |
| 354 | + } |
| 355 | + |
| 356 | + chip::app::DataModel::NullObjectType nullResp; |
| 357 | + mOnResponse(mBridge, nullResp); |
| 358 | + } |
| 359 | + |
| 360 | + void OnError(const chip::app::CommandSender * commandSender, CHIP_ERROR error) override |
| 361 | + { |
| 362 | + if (mCalledCallback) { |
| 363 | + return; |
| 364 | + } |
| 365 | + mCalledCallback = true; |
| 366 | + |
| 367 | + mOnError(mBridge, error); |
| 368 | + } |
| 369 | + |
| 370 | + void OnDone(chip::app::CommandSender * commandSender) override { chip::Platform::Delete(this); } |
| 371 | + |
| 372 | + InvokeBridgeType * _Nonnull mBridge; |
| 373 | + |
| 374 | + typename InvokeBridgeType::SuccessCallbackType mOnResponse; |
| 375 | + MTRErrorCallback mOnError; |
| 376 | + chip::Platform::UniquePtr<chip::app::CommandSender> mCommandSender; |
| 377 | + // For reads, we ensure that we make only one data/error callback to our consumer. |
| 378 | + bool mCalledCallback = false; |
| 379 | +}; |
| 380 | + |
| 381 | +template <typename BridgeType, typename RequestDataType> |
| 382 | +CHIP_ERROR MTRStartInvokeInteraction(BridgeType * _Nonnull bridge, const RequestDataType & requestData, |
| 383 | + chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session, |
| 384 | + typename BridgeType::SuccessCallbackType successCb, MTRErrorCallback failureCb, chip::EndpointId endpoint, |
| 385 | + chip::Optional<uint16_t> timedInvokeTimeoutMs) |
| 386 | +{ |
| 387 | + auto callback = chip::Platform::MakeUnique<MTRInvokeCallback<BridgeType, typename RequestDataType::ResponseType>>( |
| 388 | + bridge, successCb, failureCb); |
| 389 | + VerifyOrReturnError(callback != nullptr, CHIP_ERROR_NO_MEMORY); |
| 390 | + |
| 391 | + auto commandSender |
| 392 | + = chip::Platform::MakeUnique<chip::app::CommandSender>(callback.get(), &exchangeManager, timedInvokeTimeoutMs.HasValue()); |
| 393 | + VerifyOrReturnError(commandSender != nullptr, CHIP_ERROR_NO_MEMORY); |
| 394 | + |
| 395 | + chip::app::CommandPathParams commandPath(endpoint, 0, RequestDataType::GetClusterId(), RequestDataType::GetCommandId(), |
| 396 | + chip::app::CommandPathFlags::kEndpointIdValid); |
| 397 | + ReturnErrorOnFailure(commandSender->AddRequestData(commandPath, requestData, timedInvokeTimeoutMs)); |
| 398 | + ReturnErrorOnFailure(commandSender->SendCommandRequest(session)); |
| 399 | + |
| 400 | + callback->AdoptCommandSender(std::move(commandSender)); |
| 401 | + callback.release(); |
| 402 | + |
| 403 | + return CHIP_NO_ERROR; |
| 404 | +}; |
| 405 | + |
263 | 406 | NS_ASSUME_NONNULL_END
|
0 commit comments