From 9343d3a02cde8d3148f25d855d8d8447f61329b1 Mon Sep 17 00:00:00 2001 From: Anush Nadathur Date: Wed, 13 Mar 2024 14:19:35 -0700 Subject: [PATCH 01/27] Added macro variants with metric support matching the ones in CodeUtils (#32553) - Added Verify/Return/Log/Succ/Exit XYZ WithMetric macro variants for use in metrics - Added unit tests for these macro variants --- src/tracing/metric_macros.h | 376 ++++++++++++++++++++++++- src/tracing/tests/TestMetricEvents.cpp | 335 +++++++++++++++++++++- 2 files changed, 700 insertions(+), 11 deletions(-) diff --git a/src/tracing/metric_macros.h b/src/tracing/metric_macros.h index 4ff85f353701b3..26dde8cd3230a0 100644 --- a/src/tracing/metric_macros.h +++ b/src/tracing/metric_macros.h @@ -27,13 +27,258 @@ #if MATTER_TRACING_ENABLED /** - * @def SuccessOrExitWithMetric(kMetriKey, error) + * @def ReturnErrorOnFailureWithMetric(kMetricKey, expr) + * + * @brief + * This macros emits the specified metric with error code and returns the error code, + * if the expression returns an error. For a CHIP_ERROR expression, this means any value + * other than CHIP_NO_ERROR. For an integer expression, this means non-zero. + * + * Example usage: + * + * @code + * ReturnErrorOnFailureWithMetric(kMetricKey, channel->SendMsg(msg)); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * does not evaluate to CHIP_NO_ERROR. Value of the metric is to the + * result of the expression. + * @param[in] expr An expression to be tested. + */ +#define ReturnErrorOnFailureWithMetric(kMetricKey, expr) \ + do \ + { \ + auto __err = (expr); \ + if (!::chip::ChipError::IsSuccess(__err)) \ + { \ + MATTER_LOG_METRIC(kMetricKey, __err); \ + return __err; \ + } \ + } while (false) + +/** + * @def ReturnLogErrorOnFailureWithMetric(kMetricKey, expr) + * + * @brief + * Returns the error code if the expression returns something different + * than CHIP_NO_ERROR. In addition, a metric is emitted with the specified metric key and + * error code as the value of the metric. + * + * Example usage: + * + * @code + * ReturnLogErrorOnFailureWithMetric(kMetricKey, channel->SendMsg(msg)); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * does not evaluate to CHIP_NO_ERROR. Value of the metric is to the + * result of the expression. + * @param[in] expr A scalar expression to be evaluated against CHIP_NO_ERROR. + */ +#define ReturnLogErrorOnFailureWithMetric(kMetricKey, expr) \ + do \ + { \ + CHIP_ERROR __err = (expr); \ + if (__err != CHIP_NO_ERROR) \ + { \ + MATTER_LOG_METRIC(kMetricKey, __err); \ + ChipLogError(NotSpecified, "%s at %s:%d", ErrorStr(__err), __FILE__, __LINE__); \ + return __err; \ + } \ + } while (false) + +/** + * @def ReturnOnFailureWithMetric(kMetricKey, expr) + * + * @brief + * Returns if the expression returns an error. For a CHIP_ERROR expression, this means any value other + * than CHIP_NO_ERROR. For an integer expression, this means non-zero. If the expression evaluates to + * anything but CHIP_NO_ERROR, a metric with the specified key is emitted along with error as the value. + * + * Example usage: + * + * @code + * ReturnOnFailureWithMetric(kMetricKey, channel->SendMsg(msg)); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * does not evaluate to CHIP_NO_ERROR. Value of the metric is to the + * result of the expression. + * @param[in] expr An expression to be tested. + */ +#define ReturnOnFailureWithMetric(kMetricKey, expr) \ + do \ + { \ + auto __err = (expr); \ + if (!::chip::ChipError::IsSuccess(__err)) \ + { \ + MATTER_LOG_METRIC(kMetricKey, __err); \ + return; \ + } \ + } while (false) + +/** + * @def VerifyOrReturnWithMetric(kMetricKey, expr, ...) + * + * @brief + * Returns from the void function if expression evaluates to false. If the expression evaluates + * to false, a metric with the specified key is emitted. + * + * Example usage: + * + * @code + * VerifyOrReturnWithMetric(kMetricKey, param != nullptr, LogError("param is nullptr")); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * to false. Value of the metric is set to false. + * @param[in] expr A Boolean expression to be evaluated. + * @param[in] ... Statements to execute before returning. Optional. + */ +#define VerifyOrReturnWithMetric(kMetricKey, expr, ...) \ + do \ + { \ + if (!(expr)) \ + { \ + MATTER_LOG_METRIC(kMetricKey, false); \ + __VA_ARGS__; \ + return; \ + } \ + } while (false) + +/** + * @def VerifyOrReturnErrorWithMetric(kMetricKey, expr, code, ...) + * + * @brief + * Returns a specified error code if expression evaluates to false. If the expression evaluates + * to false, a metric with the specified key is emitted with the value set to the code. + * + * Example usage: + * + * @code + * VerifyOrReturnErrorWithMetric(kMetricKey, param != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * to false. Value of the metric is to code. + * @param[in] expr A Boolean expression to be evaluated. + * @param[in] code A value to return if @a expr is false. + * @param[in] ... Statements to execute before returning. Optional. + */ +#define VerifyOrReturnErrorWithMetric(kMetricKey, expr, code, ...) \ + VerifyOrReturnValueWithMetric(kMetricKey, expr, code, ##__VA_ARGS__) + +/** + * @def VerifyOrReturnValueWithMetric(kMetricKey, expr, value, ...) + * + * @brief + * Returns a specified value if expression evaluates to false. If the expression evaluates + * to false, a metric with the specified key is emitted with the value set to value. + * + * Example usage: + * + * @code + * VerifyOrReturnValueWithMetric(kMetricKey, param != nullptr, Foo()); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * to false. Value of the metric is to value. + * @param[in] expr A Boolean expression to be evaluated. + * @param[in] value A value to return if @a expr is false. + * @param[in] ... Statements to execute before returning. Optional. + */ +#define VerifyOrReturnValueWithMetric(kMetricKey, expr, value, ...) \ + do \ + { \ + if (!(expr)) \ + { \ + MATTER_LOG_METRIC(kMetricKey, value); \ + __VA_ARGS__; \ + return (value); \ + } \ + } while (false) + +/** + * @def VerifyOrReturnLogErrorWithMetric(kMetricKey, expr, code) + * + * @brief + * Returns and print a specified error code if expression evaluates to false. + * If the expression evaluates to false, a metric with the specified key is emitted + * with the value set to code. + * + * Example usage: + * + * @code + * VerifyOrReturnLogErrorWithMetric(kMetricKey, param != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * to false. Value of the metric is to code. + * @param[in] expr A Boolean expression to be evaluated. + * @param[in] code A value to return if @a expr is false. + */ +#if CHIP_CONFIG_ERROR_SOURCE +#define VerifyOrReturnLogErrorWithMetric(kMetricKey, expr, code) \ + do \ + { \ + if (!(expr)) \ + { \ + MATTER_LOG_METRIC(kMetricKey, code); \ + ChipLogError(NotSpecified, "%s at %s:%d", ErrorStr(code), __FILE__, __LINE__); \ + return code; \ + } \ + } while (false) +#else // CHIP_CONFIG_ERROR_SOURCE +#define VerifyOrReturnLogErrorWithMetric(kMetricKey, expr, code) \ + do \ + { \ + if (!(expr)) \ + { \ + MATTER_LOG_METRIC(kMetricKey, code); \ + ChipLogError(NotSpecified, "%s:%d false: %" CHIP_ERROR_FORMAT, #expr, __LINE__, code.Format()); \ + return code; \ + } \ + } while (false) +#endif // CHIP_CONFIG_ERROR_SOURCE + +/** + * @def ReturnErrorCodeWithMetricIf(kMetricKey, expr, code) + * + * @brief + * Returns a specified error code if expression evaluates to true + * If the expression evaluates to true, a metric with the specified key is emitted + * with the value set to code. + * + * Example usage: + * + * @code + * ReturnErrorCodeWithMetricIf(kMetricKey, state == kInitialized, CHIP_NO_ERROR); + * ReturnErrorCodeWithMetricIf(kMetricKey, state == kInitialized, CHIP_ERROR_INCORRECT_STATE); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * to true. Value of the metric is to code. + * @param[in] expr A Boolean expression to be evaluated. + * @param[in] code A value to return if @a expr is false. + */ +#define ReturnErrorCodeWithMetricIf(kMetricKey, expr, code) \ + do \ + { \ + if (expr) \ + { \ + MATTER_LOG_METRIC(kMetricKey, code); \ + return code; \ + } \ + } while (false) + +/** + * @def SuccessOrExitWithMetric(kMetricKey, error) * * @brief * This checks for the specified error, which is expected to * commonly be successful (CHIP_NO_ERROR), and branches to * the local label 'exit' if the error is not success. - * If error is not a success, a metric with key kMetriKey is emitted with + * If error is not a success, a metric with key kMetricKey is emitted with * the error code as the value of the metric. * * Example Usage: @@ -100,9 +345,110 @@ #define VerifyOrExitWithMetric(kMetricKey, aCondition, anAction) \ nlEXPECT_ACTION(aCondition, exit, MATTER_LOG_METRIC((kMetricKey), (anAction))) -/* - * Utility Macros to support optional arguments for MATTER_LOG_METRIC_XYZ macros +/** + * @def ExitNowWithMetric(kMetricKey, ...) + * + * @brief + * This unconditionally executes @a ... and branches to the local + * label 'exit'. In addition a metric is emitted with the specified key. + * + * @note The use of this interface implies neither success nor + * failure for the overall exit status of the enclosing function + * body. + * + * Example Usage: + * + * @code + * CHIP_ERROR ReadAll(Reader& reader) + * { + * CHIP_ERROR err; + * + * while (true) + * { + * err = reader.ReadNext(); + * if (err == CHIP_ERROR_AT_END) + * ExitNowWithMetric(kMetricKey, err = CHIP_NO_ERROR); + * SuccessOrExit(err); + * DoSomething(); + * } + * + * exit: + * return err; + * } + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted. + * @param[in] ... Statements to execute. Optional. */ +#define ExitNowWithMetric(kMetricKey, ...) \ + do \ + { \ + MATTER_LOG_METRIC(kMetricKey); \ + __VA_ARGS__; \ + goto exit; \ + } while (0) + +/** + * @def LogErrorOnFailureWithMetric(kMetricKey, expr) + * + * @brief + * Logs a message if the expression returns something different than CHIP_NO_ERROR. + * In addition, a metric is emitted with the specified key and value set to result + * of the expression in case it evaluates to anything other than CHIP_NO_ERROR. + * + * Example usage: + * + * @code + * LogErrorOnFailureWithMetric(kMetricKey, channel->SendMsg(msg)); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted if the expr evaluates + * does not evaluate to CHIP_NO_ERROR. Value of the metric is to the + * result of the expression. + * @param[in] expr A scalar expression to be evaluated against CHIP_NO_ERROR. + */ +#define LogErrorOnFailureWithMetric(kMetricKey, expr) \ + do \ + { \ + CHIP_ERROR __err = (expr); \ + if (__err != CHIP_NO_ERROR) \ + { \ + MATTER_LOG_METRIC(kMetricKey, __err); \ + ChipLogError(NotSpecified, "%s at %s:%d", ErrorStr(__err), __FILE__, __LINE__); \ + } \ + } while (false) + +/** + * @def VerifyOrDoWithMetric(kMetricKey, expr, ...) + * + * @brief + * Do something if expression evaluates to false. If the expression evaluates to false a metric + * with the specified key is emitted with value set to false. + * + * Example usage: + * + * @code + * VerifyOrDoWithMetric(param != nullptr, LogError("param is nullptr")); + * @endcode + * + * @param[in] kMetricKey Metric key for the metric event to be emitted. + * Value of the metric is set to false. + * @param[in] expr A Boolean expression to be evaluated. + * @param[in] ... Statements to execute. + */ +#define VerifyOrDoWithMetric(kMetricKey, expr, ...) \ + do \ + { \ + if (!(expr)) \ + { \ + MATTER_LOG_METRIC(kMetricKey, false); \ + __VA_ARGS__; \ + } \ + } while (false) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Utility Macros to support optional arguments for MATTER_LOG_METRIC_XYZ macros +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Utility to always return the 4th argument from macro parameters #define __GET_4TH_ARG(_a1, _a2, _a3, _a4, ...) _a4 @@ -222,10 +568,32 @@ // Remap Success, Return, and Verify macros to the ones without metrics //////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define ReturnErrorOnFailureWithMetric(kMetricKey, expr) ReturnErrorOnFailure(expr) + +#define ReturnLogErrorOnFailureWithMetric(kMetricKey, expr) ReturnLogErrorOnFailure(expr) + +#define ReturnOnFailureWithMetric(kMetricKey, expr) ReturnOnFailure(expr) + +#define VerifyOrReturnWithMetric(kMetricKey, expr, ...) VerifyOrReturn(expr, ##__VA_ARGS__) + +#define VerifyOrReturnErrorWithMetric(kMetricKey, expr, code, ...) VerifyOrReturnValue(expr, code, ##__VA_ARGS__) + +#define VerifyOrReturnValueWithMetric(kMetricKey, expr, value, ...) VerifyOrReturnValue(expr, value, ##__VA_ARGS__) + +#define VerifyOrReturnLogErrorWithMetric(kMetricKey, expr, code) VerifyOrReturnLogError(expr, code) + +#define ReturnErrorCodeWithMetricIf(kMetricKey, expr, code) ReturnErrorCodeIf(expr, code) + #define SuccessOrExitWithMetric(kMetricKey, aStatus) SuccessOrExit(aStatus) #define VerifyOrExitWithMetric(kMetricKey, aCondition, anAction) VerifyOrExit(aCondition, anAction) +#define ExitNowWithMetric(kMetricKey, ...) ExitNow(##__VA_ARGS__) + +#define LogErrorOnFailureWithMetric(kMetricKey, expr) LogErrorOnFailure(expr) + +#define VerifyOrDoWithMetric(kMetricKey, expr, ...) VerifyOrDo(expr, ##__VA_ARGS__) + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Map all MATTER_LOG_METRIC_XYZ macros to noops //////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/tracing/tests/TestMetricEvents.cpp b/src/tracing/tests/TestMetricEvents.cpp index c52a5fc91b9e93..72e159df7cf7fa 100644 --- a/src/tracing/tests/TestMetricEvents.cpp +++ b/src/tracing/tests/TestMetricEvents.cpp @@ -324,14 +324,335 @@ void TestSuccessOrExitWithMetric(nlTestSuite * inSuite, void * inContext) inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); } +static CHIP_ERROR InvokeReturnErrorOnFailureWithMetric(MetricKey key, const CHIP_ERROR & error) +{ + ReturnErrorOnFailureWithMetric(key, error); + return CHIP_NO_ERROR; +} + +void TestReturnErrorOnFailureWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + CHIP_ERROR err = InvokeReturnErrorOnFailureWithMetric("event0", CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = InvokeReturnErrorOnFailureWithMetric("event1", CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + + err = InvokeReturnErrorOnFailureWithMetric("event2", CHIP_ERROR_BAD_REQUEST); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BAD_REQUEST); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event1", CHIP_ERROR_INCORRECT_STATE), + MetricEvent(MetricEvent::Type::kInstantEvent, "event2", CHIP_ERROR_BAD_REQUEST), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +static CHIP_ERROR InvokeReturnLogErrorOnFailureWithMetric(MetricKey key, const CHIP_ERROR & error) +{ + ReturnLogErrorOnFailureWithMetric(key, error); + return CHIP_NO_ERROR; +} + +void TestReturnLogErrorOnFailureWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + CHIP_ERROR err = InvokeReturnLogErrorOnFailureWithMetric("event0", CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = InvokeReturnLogErrorOnFailureWithMetric("event1", CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + + err = InvokeReturnLogErrorOnFailureWithMetric("event2", CHIP_ERROR_BAD_REQUEST); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BAD_REQUEST); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event1", CHIP_ERROR_INCORRECT_STATE), + MetricEvent(MetricEvent::Type::kInstantEvent, "event2", CHIP_ERROR_BAD_REQUEST), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +static void InvokeReturnOnFailureWithMetric(MetricKey key, const CHIP_ERROR & error) +{ + ReturnOnFailureWithMetric(key, error); + return; +} + +void TestReturnOnFailureWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + InvokeReturnOnFailureWithMetric("event0", CHIP_NO_ERROR); + + InvokeReturnOnFailureWithMetric("event1", CHIP_ERROR_INCORRECT_STATE); + + InvokeReturnOnFailureWithMetric("event2", CHIP_ERROR_BAD_REQUEST); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event1", CHIP_ERROR_INCORRECT_STATE), + MetricEvent(MetricEvent::Type::kInstantEvent, "event2", CHIP_ERROR_BAD_REQUEST), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +static void InvokeVerifyOrReturnWithMetric(MetricKey key, bool result) +{ + VerifyOrReturnWithMetric(key, result); + return; +} + +void TestInvokeVerifyOrReturnWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + InvokeVerifyOrReturnWithMetric("event0", DoubleOf(2) == 4); + InvokeVerifyOrReturnWithMetric("event1", DoubleOf(3) == 9); + InvokeVerifyOrReturnWithMetric("event2", DoubleOf(4) == 8); + InvokeVerifyOrReturnWithMetric("event3", DoubleOf(5) == 11); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event1", false), + MetricEvent(MetricEvent::Type::kInstantEvent, "event3", false), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +static CHIP_ERROR InvokeVerifyOrReturnErrorWithMetric(MetricKey key, bool expr, const CHIP_ERROR & error) +{ + VerifyOrReturnErrorWithMetric(key, expr, error); + return CHIP_NO_ERROR; +} + +void TestVerifyOrReturnErrorWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + CHIP_ERROR err = InvokeVerifyOrReturnErrorWithMetric("event0", DoubleOf(2) == 4, CHIP_ERROR_BAD_REQUEST); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = InvokeVerifyOrReturnErrorWithMetric("event1", DoubleOf(3) == 9, CHIP_ERROR_ACCESS_DENIED); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_ACCESS_DENIED); + + err = InvokeVerifyOrReturnErrorWithMetric("event2", DoubleOf(4) == 8, CHIP_ERROR_BUSY); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = InvokeVerifyOrReturnErrorWithMetric("event3", DoubleOf(5) == 11, CHIP_ERROR_CANCELLED); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CANCELLED); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event1", CHIP_ERROR_ACCESS_DENIED), + MetricEvent(MetricEvent::Type::kInstantEvent, "event3", CHIP_ERROR_CANCELLED), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +template +static return_code_type InvokeVerifyOrReturnValueWithMetric(MetricKey key, bool expr, return_code_type retval) +{ + VerifyOrReturnValueWithMetric(key, expr, retval); + return return_code_type(); +} + +void TestVerifyOrReturnValueWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + auto retval = InvokeVerifyOrReturnValueWithMetric("event0", DoubleOf(2) == 4, 0); + NL_TEST_ASSERT(inSuite, retval == 0); + + auto err = InvokeVerifyOrReturnValueWithMetric("event1", DoubleOf(3) == 9, CHIP_ERROR_ACCESS_DENIED); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_ACCESS_DENIED); + + err = InvokeVerifyOrReturnValueWithMetric("event2", DoubleOf(4) == 8, CHIP_ERROR_BAD_REQUEST); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + retval = InvokeVerifyOrReturnValueWithMetric("event3", DoubleOf(5) == 11, 16); + NL_TEST_ASSERT(inSuite, retval == 16); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event1", CHIP_ERROR_ACCESS_DENIED), + MetricEvent(MetricEvent::Type::kInstantEvent, "event3", 16), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +static CHIP_ERROR InvokeVerifyOrReturnLogErrorWithMetric(MetricKey key, bool expr, const CHIP_ERROR & error) +{ + VerifyOrReturnLogErrorWithMetric(key, expr, error); + return CHIP_NO_ERROR; +} + +void TestVerifyOrReturnLogErrorWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + auto err = InvokeVerifyOrReturnLogErrorWithMetric("event0", DoubleOf(2) == 4, CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = InvokeVerifyOrReturnLogErrorWithMetric("event1", DoubleOf(3) == 9, CHIP_ERROR_CANCELLED); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CANCELLED); + + err = InvokeVerifyOrReturnLogErrorWithMetric("event2", DoubleOf(4) == 8, CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = InvokeVerifyOrReturnLogErrorWithMetric("event3", DoubleOf(5) == 11, CHIP_ERROR_BAD_REQUEST); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BAD_REQUEST); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event1", CHIP_ERROR_CANCELLED), + MetricEvent(MetricEvent::Type::kInstantEvent, "event3", CHIP_ERROR_BAD_REQUEST), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +template +static return_code_type InvokeReturnErrorCodeWithMetricIf(MetricKey key, bool expr, const return_code_type & code) +{ + ReturnErrorCodeWithMetricIf(key, expr, code); + return return_code_type(); +} + +void TestReturnErrorCodeWithMetricIf(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + auto err = InvokeReturnErrorCodeWithMetricIf("event0", DoubleOf(2) == 4, CHIP_ERROR_DUPLICATE_KEY_ID); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_DUPLICATE_KEY_ID); + + auto retval = InvokeReturnErrorCodeWithMetricIf("event1", DoubleOf(3) == 9, 11); + NL_TEST_ASSERT(inSuite, retval == 0); + + retval = InvokeReturnErrorCodeWithMetricIf("event2", DoubleOf(4) == 8, 22); + NL_TEST_ASSERT(inSuite, retval == 22); + + err = InvokeReturnErrorCodeWithMetricIf("event3", DoubleOf(5) == 11, CHIP_ERROR_ACCESS_DENIED); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event0", CHIP_ERROR_DUPLICATE_KEY_ID), + MetricEvent(MetricEvent::Type::kInstantEvent, "event2", 22), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +void TestExitNowWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + chip::ChipError err = CHIP_NO_ERROR; + + ExitNowWithMetric("event0", err = CHIP_ERROR_BUSY); + +exit: + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event0"), + }; + + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_BUSY); + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +void TestLogErrorOnFailureWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + LogErrorOnFailureWithMetric("event0", CHIP_ERROR_BAD_REQUEST); + LogErrorOnFailureWithMetric("event1", CHIP_NO_ERROR); + LogErrorOnFailureWithMetric("event2", CHIP_ERROR_DATA_NOT_ALIGNED); + LogErrorOnFailureWithMetric("event3", CHIP_ERROR_BUSY); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event0", CHIP_ERROR_BAD_REQUEST), + MetricEvent(MetricEvent::Type::kInstantEvent, "event2", CHIP_ERROR_DATA_NOT_ALIGNED), + MetricEvent(MetricEvent::Type::kInstantEvent, "event3", CHIP_ERROR_BUSY), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + +void TestVerifyOrDoWithMetric(nlTestSuite * inSuite, void * inContext) +{ + MetricEventBackend backend; + ScopedRegistration scope(backend); + + VerifyOrDoWithMetric("event0", DoubleOf(2) == 5); + VerifyOrDoWithMetric("event1", DoubleOf(3) == 6); + VerifyOrDoWithMetric("event2", DoubleOf(4) == 7, MATTER_LOG_METRIC("event4", 10)); + VerifyOrDoWithMetric("event3", DoubleOf(5) == 8); + VerifyOrDoWithMetric("event5", DoubleOf(6) == 12); + + std::vector expected = { + MetricEvent(MetricEvent::Type::kInstantEvent, "event0", false), + MetricEvent(MetricEvent::Type::kInstantEvent, "event2", false), + MetricEvent(MetricEvent::Type::kInstantEvent, "event4", 10), + MetricEvent(MetricEvent::Type::kInstantEvent, "event3", false), + }; + + NL_TEST_ASSERT(inSuite, backend.GetMetricEvents().size() == expected.size()); + NL_TEST_ASSERT( + inSuite, std::equal(backend.GetMetricEvents().begin(), backend.GetMetricEvents().end(), expected.begin(), expected.end())); +} + static const nlTest sMetricTests[] = { - NL_TEST_DEF("BasicMetricEvent", TestBasicMetricEvent), // - NL_TEST_DEF("InstantMetricEvent", TestInstantMetricEvent), // - NL_TEST_DEF("BeginEndMetricEvent", TestBeginEndMetricEvent), // - NL_TEST_DEF("ScopedMetricEvent", TestScopedMetricEvent), // - NL_TEST_DEF("VerifyOrExitWithMetric", TestVerifyOrExitWithMetric), // - NL_TEST_DEF("SuccessOrExitWithMetric", TestSuccessOrExitWithMetric), // - NL_TEST_SENTINEL() // + NL_TEST_DEF("BasicMetricEvent", TestBasicMetricEvent), // + NL_TEST_DEF("InstantMetricEvent", TestInstantMetricEvent), // + NL_TEST_DEF("BeginEndMetricEvent", TestBeginEndMetricEvent), // + NL_TEST_DEF("ScopedMetricEvent", TestScopedMetricEvent), // + NL_TEST_DEF("VerifyOrExitWithMetric", TestVerifyOrExitWithMetric), // + NL_TEST_DEF("SuccessOrExitWithMetric", TestSuccessOrExitWithMetric), // + NL_TEST_DEF("ReturnErrorOnFailureWithMetric", TestReturnErrorOnFailureWithMetric), // + NL_TEST_DEF("ReturnLogErrorOnFailureWithMetric", TestReturnLogErrorOnFailureWithMetric), // + NL_TEST_DEF("ReturnOnFailureWithMetric", TestReturnOnFailureWithMetric), // + NL_TEST_DEF("VerifyOrReturnWithMetric", TestInvokeVerifyOrReturnWithMetric), // + NL_TEST_DEF("VerifyOrReturnErrorWithMetric", TestVerifyOrReturnErrorWithMetric), // + NL_TEST_DEF("VerifyOrReturnValueWithMetric", TestVerifyOrReturnValueWithMetric), // + NL_TEST_DEF("VerifyOrReturnLogErrorWithMetric", TestVerifyOrReturnLogErrorWithMetric), // + NL_TEST_DEF("ReturnErrorCodeWithMetricIf", TestReturnErrorCodeWithMetricIf), // + NL_TEST_DEF("ExitNowWithMetric", TestExitNowWithMetric), // + NL_TEST_DEF("LogErrorOnFailureWithMetric", TestLogErrorOnFailureWithMetric), // + NL_TEST_DEF("VerifyOrDoWithMetric", TestVerifyOrDoWithMetric), // + NL_TEST_SENTINEL() // }; } // namespace From eecc2a6dbba7bcb8b00cdab6e5547d097190bdcc Mon Sep 17 00:00:00 2001 From: Justin Wood Date: Wed, 13 Mar 2024 15:06:38 -0700 Subject: [PATCH 02/27] More mistakes in documentation (#32563) * Initial commit * Restyled by whitespace * Restyled by clang-format * Update src/darwin/Framework/CHIP/MTRDevice.mm Co-authored-by: Anush Nadathur * Update src/darwin/Framework/CHIP/MTRDevice.h Co-authored-by: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com> * Adding header * Restyled by clang-format * Updating docs * Fixing missing language --------- Co-authored-by: Restyled.io Co-authored-by: Anush Nadathur Co-authored-by: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com> --- src/darwin/Framework/CHIP/MTRDevice.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDevice.h b/src/darwin/Framework/CHIP/MTRDevice.h index 3bb39e982bcf98..989ed44f7667be 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.h +++ b/src/darwin/Framework/CHIP/MTRDevice.h @@ -70,8 +70,8 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) * This will be true after the deviceCachePrimed: delegate callback has been called, false if not. * * Please note if you have a storage delegate implemented, the cache is then stored persistently, so - * the would then only be called once, ever - and this property would basically always be true - * if a subscription has ever been established. + * the delegate would then only be called once, ever - and this property would basically always be true + * if a subscription has ever been established at any point in the past. * */ @property (readonly) BOOL deviceCachePrimed MTR_NEWLY_AVAILABLE; From c90481f6a5356cbddaae2d461db1fb3982432dd4 Mon Sep 17 00:00:00 2001 From: Raul Marquez <130402456+raul-marquez-csa@users.noreply.github.com> Date: Wed, 13 Mar 2024 21:31:34 -0700 Subject: [PATCH 03/27] Matter SDK Doctor (#32554) * Adds Matter SDK Doctor * Fix restyle * Fix restyle * Fix restyle * Fix restyle --- scripts/sdk-doctor/_network.sh | 80 ++++++++++++++++ scripts/sdk-doctor/_os.sh | 69 ++++++++++++++ scripts/sdk-doctor/_repo.sh | 152 ++++++++++++++++++++++++++++++ scripts/sdk-doctor/_version.sh | 27 ++++++ scripts/sdk-doctor/sdk-doctor.sh | 153 +++++++++++++++++++++++++++++++ 5 files changed, 481 insertions(+) create mode 100755 scripts/sdk-doctor/_network.sh create mode 100755 scripts/sdk-doctor/_os.sh create mode 100755 scripts/sdk-doctor/_repo.sh create mode 100755 scripts/sdk-doctor/_version.sh create mode 100755 scripts/sdk-doctor/sdk-doctor.sh diff --git a/scripts/sdk-doctor/_network.sh b/scripts/sdk-doctor/_network.sh new file mode 100755 index 00000000000000..940161ca5c7cea --- /dev/null +++ b/scripts/sdk-doctor/_network.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# +# Copyright (c) 2024 Project CHIP Authors +# +# 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. + +# Function to display information for each network interface +display_interface_info() { + interface=$1 + echo "Interface: $interface" + + # Check if interface is up + if ip link show "$interface" | grep -q 'state UP'; then + echo " Status: UP" + else + echo " Status: DOWN" + fi + + # Get and display the IPv4 address + ipv4_address=$(ip -4 addr show "$interface" | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + [ -z "$ipv4_address" ] && ipv4_address="N/A" + echo " IPv4 Address: $ipv4_address" + + # Get and display the IPv6 address + ipv6_address=$(ip -6 addr show "$interface" | grep -oP '(?<=inet6\s)[a-f0-9:]+') + [ -z "$ipv6_address" ] && ipv6_address="N/A" + echo " IPv6 Address: $ipv6_address" + + # Get and display the subnet mask + subnet_mask=$(ifconfig "$interface" | grep -oP '(?<=Mask:)[0-9.]+') + [ -z "$subnet_mask" ] && subnet_mask="N/A" + echo " Subnet Mask: $subnet_mask" + + # Get and display the MAC address + mac_address=$(ip link show "$interface" | grep -oP '(?<=ether\s)[a-f0-9:]+') + [ -z "$mac_address" ] && mac_address="N/A" + echo " MAC Address: $mac_address" +} + +# Get a list of all network interfaces +interfaces=$(ip link show | grep -oP '(?<=^\d: )[e-w]+[0-9a-zA-Z-]+') + +# Iterate over each interface and display relevant information +for intf in "$interfaces"; do + display_interface_info "$intf" + echo "" +done + +# Get and display the default gateway +default_gateway=$(ip route | grep default | awk '{print $3}') +[ -z "$default_gateway" ] && default_gateway="N/A" +echo "Default Gateway: $default_gateway" + +# Get and display the DNS server information +mapfile -t dns_servers < <(grep nameserver /etc/resolv.conf | awk '{print $2}') +if [ ${#dns_servers[@]} -eq 0 ]; then + echo "DNS Servers: N/A" +else + echo "DNS Servers: ${dns_servers[*]}" +fi +echo + +# Check if Internet is available +echo "Checking Internet availability..." +if ping -c 1 8.8.8.8 &>/dev/null; then + echo "Internet is available" +else + echo "Internet is not available" +fi diff --git a/scripts/sdk-doctor/_os.sh b/scripts/sdk-doctor/_os.sh new file mode 100755 index 00000000000000..e22da32e743cfa --- /dev/null +++ b/scripts/sdk-doctor/_os.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# +# Copyright (c) 2024 Project CHIP Authors +# +# 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. + +ROOT_DIR=$(realpath "$(dirname "$0")"/../..) +cd "$ROOT_DIR" + +# Function to display OS information +get_os_info() { + if [ -f /etc/os-release ]; then + # If available, use /etc/os-release file + . /etc/os-release + echo "Name: $NAME" + echo "Version: $VERSION" + echo "ID: $ID" + echo "ID Like: $ID_LIKE" + elif [ -f /etc/*-release ]; then + # If /etc/os-release is not available, use other available /etc/*-release file + echo "OS Information from /etc/*-release:" + cat /etc/*-release + else + # Print a message if unable to determine OS information + echo "Cannot determine OS information." + fi +} + +# Function to display kernel information +get_kernel_info() { + echo "Kernel Information:" + uname -a | fold -w 88 -s +} + +# Function to display CPU information +get_cpu_info() { + echo "CPU Information:" + lscpu | grep -E "^Architecture:|^CPU op-mode\(s\):|^CPU\(s\):|^Vendor ID:|^Model name:|^CPU max MHz:|^CPU min MHz:" | + sed 's/^[ \t]*//;s/[ \t]*$//' +} + +# Function to display memory information +get_memory_info() { + echo "Memory Information:" + free -h +} + +# Call the functions to display the information +get_os_info +echo + +get_kernel_info +echo + +get_cpu_info +echo + +get_memory_info diff --git a/scripts/sdk-doctor/_repo.sh b/scripts/sdk-doctor/_repo.sh new file mode 100755 index 00000000000000..de619387274117 --- /dev/null +++ b/scripts/sdk-doctor/_repo.sh @@ -0,0 +1,152 @@ +#!/bin/bash + +# +# Copyright (c) 2024 Project CHIP Authors +# +# 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. + +ROOT_DIR=$(realpath "$(dirname "$0")"/../..) +cd "$ROOT_DIR" + +get_repo_and_branch_info() { + # Input validation + if [ -z "$1" ]; then + echo "Please provide a path." + return 1 + fi + + path="$1" + repo_friendly_name="Matter SDK" + + if [ "$path" != "." ]; then + title_case_path=$(echo "$path" | awk '{ for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2)); }1') + repo_friendly_name=$title_case_path + fi + + # Check if the directory exists + if [ ! -d "$path" ]; then + echo "Directory '$path' does not exist." + return 1 + fi + + cd "$path" + + # Get the URL of the remote origin + remote_url=$(git config --get remote.origin.url) + + if [ -n "$remote_url" ]; then + # Extract the repository name from the URL + repo_name=$(basename -s .git "$remote_url") + + # Calculate the necessary padding to align the end pipe + total_length=95 # Adjust this based on your frame width + text_length=${#repo_friendly_name}+${#repo_name}+4 # 4 for the ": " and two spaces around the repo name + padding_length=$((total_length - text_length)) + + echo '+-----------------------------------------------------------------------------------------------+' + printf "| %s: %s%*s|\n" "$repo_friendly_name" "$repo_name" "$padding_length" "" + echo '+-----------------------------------------------------------------------------------------------+' + else + # Print error message if there is no remote URL + echo "Not a Git repository or no remote set" + return 1 + fi + + # Get the current branch and its tracking branch + git_status=$(git status) + tracking_branch_info=$(echo "$git_status" | grep "Your branch is up to date with") + + # Extract the fork owner and branch from the tracking branch info + if [[ $tracking_branch_info =~ Your\ branch\ is\ up\ to\ date\ with\ \'([^\']+)\' ]]; then + fork_owner_and_branch="${BASH_REMATCH[1]}" + else + fork_owner_and_branch="Not set or not a tracking branch" + fi + + # Get the commit SHA of the current HEAD + commit_sha=$(git rev-parse HEAD) + echo "Commit SHA: $commit_sha" + + # Get the commit message of the current HEAD + commit_message=$(git log -1 --pretty=format:"%B") + trimmed_commit_message=$(trim_commit_message "$commit_message") + echo "Commit Message: $trimmed_commit_message" + + # Get the commit author of the current HEAD + commit_author=$(git log -1 --pretty=format:"%an") + echo "Commit Author: $commit_author" + + # Get the commit date and time of the current HEAD including timezone + commit_datetime=$(git log -1 --pretty=format:"%cd" --date=format:"%Y-%m-%d %H:%M:%S %z") + echo "Commit Date: $commit_datetime" + + # Attempt to find branches that contain this commit + branches=$(git branch --contains "$commit_sha" | sed 's/^/ /') + + if [ -n "$branches" ]; then + echo "Contained in branches:" + echo "$branches" + else + echo "This commit is not on any known branch." + fi + + echo " Tracking: $fork_owner_and_branch" + + echo + + # Navigate back to the original directory + cd "$ROOT_DIR" +} + +trim_commit_message() { + local commit_message="$1" + + # Check if the commit message contains a newline character + if [[ "$commit_message" == *$'\n'* ]]; then + # Extract the first line of the commit message + local first_line="${commit_message%%$'\n'*}" + else + # If there's no newline, use the entire commit message + local first_line="$commit_message" + fi + + # Trim leading and trailing whitespace from the first line and echo it + echo "$first_line" | sed 's/^[ \t]*//;s/[ \t]*$//' +} + +# Print SDK root git status +get_repo_and_branch_info "." + +# Handle arguments +case "$1" in + --git-sub) + # Initialize an array to hold the directories + declare -a repo_dirs + + cd "$ROOT_DIR" + + # Find directories containing a .github folder and store them in the array, excluding the current directory + while IFS= read -r dir; do + # Check if the directory is not the current directory + if [[ "$dir" != "." ]]; then + repo_dirs+=("$dir") + fi + done < <(find . -type d -name .github | awk -F'/[^/]*$' '{print $1}') + + # Iterate through the directories and call the function for each + for dir in "${repo_dirs[@]}"; do + get_repo_and_branch_info "$dir" + done + ;; + *) ;; +esac diff --git a/scripts/sdk-doctor/_version.sh b/scripts/sdk-doctor/_version.sh new file mode 100755 index 00000000000000..c83f1804b7d46b --- /dev/null +++ b/scripts/sdk-doctor/_version.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# +# Copyright (c) 2024 Project CHIP Authors +# +# 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. + +ROOT_DIR=$(realpath "$(dirname "$0")"/../..) +cd "$ROOT_DIR" + +SPEC_VERSION=$(head -n 1 "$ROOT_DIR"/SPECIFICATION_VERSION) +SPEC_SHA=$(head -n 1 "$ROOT_DIR"/data_model/spec_sha) +SCRAPPER_VERSION=$(head -n 1 "$ROOT_DIR"/data_model/scraper_version) + +echo 'SPEC VERSION:' "$SPEC_VERSION" +echo 'SPEC SHA:' "$SPEC_SHA" +echo 'SCRAPER VERSION:' "$SCRAPPER_VERSION" diff --git a/scripts/sdk-doctor/sdk-doctor.sh b/scripts/sdk-doctor/sdk-doctor.sh new file mode 100755 index 00000000000000..597ec81c67fc58 --- /dev/null +++ b/scripts/sdk-doctor/sdk-doctor.sh @@ -0,0 +1,153 @@ +#!/bin/bash + +# +# Copyright (c) 2024 Project CHIP Authors +# +# 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. + +{ + echo " __ __ _ _ _____ _____ _ __ _____ _ " + echo " | \/ | | | | | / ____| __ \| |/ / | __ \ | | " + echo " | \ / | __ _| |_| |_ ___ _ __ | (___ | | | | ' / | | | | ___ ___| |_ ___ _ __ " + echo ' | |\/| |/ _` | __| __/ _ \ `__| \___ \| | | | < | | | |/ _ \ / __| __/ _ \| `__|' + echo " | | | | (_| | |_| || __/ | ____) | |__| | . \ | |__| | (_) | (__| || (_) | | " + echo " |_| |_|\__,_|\__|\__\___|_| |_____/|_____/|_|\_\ |_____/ \___/ \___|\__\___/|_| " + echo " " + + usage() { + echo "Displays Matter SDK setup information." + echo + echo "Usage:" + echo + echo " Show Matter SDK Version, SHA, repository details: $0" + echo " Show Matter SDK git submodule information: $0 --git-sub" + echo " Show Matter SDK host OS system information: $0 --system" + echo " Show all available Matter SDK details: $0 --complete" + echo + } + + width=104 + + # Get the date string + date_string=$(date) + + # Calculate the length of the date string + date_length=${#date_string} + + # Calculate the padding on each side + padding=$(((width - date_length) / 2)) + + # Print spaces for left padding + printf "%${padding}s" + + # Print the date string + echo "$date_string" + echo + + ROOT_DIR=$(realpath "$(dirname "$0")"/../..) + TH_DEV_SCRIPTS_DIR=$ROOT_DIR/scripts/sdk-doctor + cd "$ROOT_DIR" + + # Check for arguments + if [ "$#" -gt 1 ]; then + echo "Error: Too many arguments." + usage + exit 1 + fi + + print_framed_text() { + # Get the text and title from function arguments + input_text="$1" + title="$2" + + # Ensure 'width' is set and has a reasonable value + if [ -z "$width" ] || [ "$width" -lt 10 ]; then + echo "Error: 'width' is not set or too small." + return 1 + fi + + max_line_length=$((width - 6)) # Maximum characters in a line before wrapping + + # Word-wrap the input text + input_text_wrapped=$(echo -e "$input_text" | fold -w "$max_line_length" -s) + + # Calculate height based on the number of lines in the input text + height=$(echo -e "$input_text_wrapped" | wc -l) + height=$((height + 4)) # Add 4 to account for the top and bottom frame borders and inner padding + + # Print the top border with title + title_with_padding=" $title " + title_padding_left=$(((width - 2 - ${#title_with_padding}) / 2)) + [ "$title_padding_left" -lt 0 ] && title_padding_left=0 + title_padding_right=$((width - 2 - ${#title_with_padding} - title_padding_left)) + [ "$title_padding_right" -lt 0 ] && title_padding_right=0 + echo '+'"$(printf "%0.s-" "$(seq 1 "$title_padding_left")")$title_with_padding""$(printf "%0.s-" "$(seq 1 "$title_padding_right")")"'+' + + # Inner top padding + echo "|$(printf ' %.0s' "$(seq 1 $((width - 2)))")|" + + # Print each line of wrapped input text with frame borders and padding + echo -e "$input_text_wrapped" | while IFS= read -r line; do + padding_right=$((width - 4 - ${#line} - 2)) # Subtract 4 for the borders and 2 for the left padding + [ "$padding_right" -lt 0 ] && padding_right=0 + echo "| $line$(printf ' %.0s' "$(seq 1 "$padding_right")") |" + done + + # Inner bottom padding + echo "|$(printf ' %.0s' "$(seq 1 $((width - 2)))")|" + + # Print the bottom border + echo '+'"$(printf "%0.s-" "$(seq 1 $((width - 2)))")"'+' + echo + } + + show_system() { + # OS + os_output=$("$TH_DEV_SCRIPTS_DIR/_os.sh") + print_framed_text "$os_output" "OS" + + # Network + network_output=$("$TH_DEV_SCRIPTS_DIR/_network.sh") + print_framed_text "$network_output" "Network" + } + + # Matter SDK Version + th_version_output=$("$TH_DEV_SCRIPTS_DIR/_version.sh") + print_framed_text "$th_version_output" "Matter SDK Version" + + # Git Status + if [[ "$1" = "--git-sub" ]] || [[ "$1" = "--complete" ]]; then + repository_branches_output=$("$TH_DEV_SCRIPTS_DIR/_repo.sh" "--git-sub") + print_framed_text "$repository_branches_output" "Git Status" + else + repository_branches_output=$("$TH_DEV_SCRIPTS_DIR/_repo.sh" "--git") + print_framed_text "$repository_branches_output" "Git Status" + fi + + # Handle arguments + case "$1" in + --system) + show_system + usage + ;; + --complete) + show_system + usage + ;; + *) + usage + exit 1 + ;; + esac + +} 2>&1 | tee sdk-doctor.txt From cdfca0f1c5ee43bad4c57aa53ee70b7129043206 Mon Sep 17 00:00:00 2001 From: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com> Date: Fri, 15 Mar 2024 09:07:57 +1300 Subject: [PATCH 04/27] Work around TSAN ASLR issue in CI (#32567) * Work around TSAN ASLR issue * Simplify action condition --------- Co-authored-by: joonhaengHeo <85541460+joonhaengHeo@users.noreply.github.com> --- .../action.yaml | 7 ++ .../actions/maximize-runner-disk/action.yaml | 72 +++++++++---------- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/.github/actions/checkout-submodules-and-bootstrap/action.yaml b/.github/actions/checkout-submodules-and-bootstrap/action.yaml index b514b8dd795509..f88be1eded25e6 100644 --- a/.github/actions/checkout-submodules-and-bootstrap/action.yaml +++ b/.github/actions/checkout-submodules-and-bootstrap/action.yaml @@ -41,3 +41,10 @@ runs: uses: ./.github/actions/upload-bootstrap-logs with: bootstrap-log-name: ${{ inputs.bootstrap-log-name }} + - name: Work around TSAN ASLR issues + if: runner.os == 'Linux' && !env.ACT + shell: bash + run: | + # See https://stackoverflow.com/a/77856955/2365113 + if [[ "$UID" == 0 ]]; then function sudo() { "$@"; }; fi + sudo sysctl vm.mmap_rnd_bits=28 diff --git a/.github/actions/maximize-runner-disk/action.yaml b/.github/actions/maximize-runner-disk/action.yaml index fe5f95352aa53e..d71ba3646d3279 100644 --- a/.github/actions/maximize-runner-disk/action.yaml +++ b/.github/actions/maximize-runner-disk/action.yaml @@ -4,47 +4,45 @@ runs: using: "composite" steps: - name: Free up disk space on the github runner - if: ${{ !env.ACT }} + if: runner.os == 'Linux' && !env.ACT shell: bash run: | # maximize-runner-disk - if [[ "$RUNNER_OS" == Linux ]]; then - # Directories to prune to free up space. Candidates: - # 1.6G /usr/share/dotnet - # 1.1G /usr/local/lib/android/sdk/platforms - # 1000M /usr/local/lib/android/sdk/build-tools - # 8.9G /usr/local/lib/android/sdk - # This list can be amended later to change the trade-off between the amount of - # disk space freed up, and how long it takes to do so (deleting many files is slow). - prune=(/usr/share/dotnet /usr/local/lib/android/sdk/platforms /usr/local/lib/android/sdk/build-tools) + # Directories to prune to free up space. Candidates: + # 1.6G /usr/share/dotnet + # 1.1G /usr/local/lib/android/sdk/platforms + # 1000M /usr/local/lib/android/sdk/build-tools + # 8.9G /usr/local/lib/android/sdk + # This list can be amended later to change the trade-off between the amount of + # disk space freed up, and how long it takes to do so (deleting many files is slow). + prune=(/usr/share/dotnet /usr/local/lib/android/sdk/platforms /usr/local/lib/android/sdk/build-tools) - if [[ "$UID" -eq 0 && -d /__w ]]; then - root=/runner-root-volume - if [[ ! -d "$root" ]]; then - echo "Unable to maximize disk space, job is running inside a container and $root is not mounted" - exit 0 - fi - function sudo() { "$@"; } # we're already root (and sudo is probably unavailable) - elif [[ "$UID" -ne 0 && "$RUNNER_ENVIRONMENT" == github-hosted ]]; then - root= - else - echo "Unable to maximize disk space, unknown runner environment" + if [[ "$UID" -eq 0 && -d /__w ]]; then + root=/runner-root-volume + if [[ ! -d "$root" ]]; then + echo "Unable to maximize disk space, job is running inside a container and $root is not mounted" exit 0 fi - - echo "Freeing up runner disk space on ${root:-/}" - function avail() { df -k --output=avail "${root:-/}" | grep '^[0-9]*$'; } - function now() { date '+%s'; } - before="$(avail)" start="$(now)" - for dir in "${prune[@]}"; do - if [[ -d "${root}${dir}" ]]; then - echo "- $dir" - # du -sh -- "${root}${dir}" - sudo rm -rf -- "${root}${dir}" - else - echo "- $dir (not found)" - fi - done - after="$(avail)" end="$(now)" - echo "Done, freed up $(( (after - before) / 1024 ))M of disk space in $(( end - start )) seconds." + function sudo() { "$@"; } # we're already root (and sudo is probably unavailable) + elif [[ "$UID" -ne 0 && "$RUNNER_ENVIRONMENT" == github-hosted ]]; then + root= + else + echo "Unable to maximize disk space, unknown runner environment" + exit 0 fi + + echo "Freeing up runner disk space on ${root:-/}" + function avail() { df -k --output=avail "${root:-/}" | grep '^[0-9]*$'; } + function now() { date '+%s'; } + before="$(avail)" start="$(now)" + for dir in "${prune[@]}"; do + if [[ -d "${root}${dir}" ]]; then + echo "- $dir" + # du -sh -- "${root}${dir}" + sudo rm -rf -- "${root}${dir}" + else + echo "- $dir (not found)" + fi + done + after="$(avail)" end="$(now)" + echo "Done, freed up $(( (after - before) / 1024 ))M of disk space in $(( end - start )) seconds." From 84970a085422d6f8088f34053cc88d06add8b0ca Mon Sep 17 00:00:00 2001 From: Terence Hampson Date: Thu, 14 Mar 2024 16:43:21 -0400 Subject: [PATCH 05/27] Fix compiler error on tip of tree (#32575) * Fix compiler error on tip of tree * Address PR comment --- src/controller/AbstractDnssdDiscoveryController.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controller/AbstractDnssdDiscoveryController.cpp b/src/controller/AbstractDnssdDiscoveryController.cpp index 2443c5fe0b1ac9..4d916e068ddbe6 100644 --- a/src/controller/AbstractDnssdDiscoveryController.cpp +++ b/src/controller/AbstractDnssdDiscoveryController.cpp @@ -34,9 +34,11 @@ void AbstractDnssdDiscoveryController::OnNodeDiscovered(const chip::Dnssd::Disco { continue; } + // TODO(#32576) Check if IP address are the same. Must account for `numIPs` in the list of `ipAddress`. + // Additionally, must NOT assume that the ordering is consistent. if (strcmp(discoveredNode.resolutionData.hostName, nodeData.resolutionData.hostName) == 0 && discoveredNode.resolutionData.port == nodeData.resolutionData.port && - discoveredNode.resolutionData.ipAddress == nodeData.resolutionData.ipAddress) + discoveredNode.resolutionData.numIPs == nodeData.resolutionData.numIPs) { discoveredNode = nodeData; if (mDeviceDiscoveryDelegate != nullptr) From 0aa09e0d99ffee39a72990250a22ee1153089f0d Mon Sep 17 00:00:00 2001 From: Justin Wood Date: Thu, 14 Mar 2024 16:56:29 -0700 Subject: [PATCH 06/27] Make MTRDevice mark itself as unreachable after 10s of waiting for a subscription (#32579) * First commit * Restyled by whitespace * Restyled by clang-format --------- Co-authored-by: Restyled.io --- src/darwin/Framework/CHIP/MTRDevice.mm | 45 +++++++++++++++++++------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 4c6b4a21e828d9..a83528b74d53e8 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -51,6 +51,8 @@ NSString * const MTRPreviousDataKey = @"previousData"; NSString * const MTRDataVersionKey = @"dataVersion"; +#define kTimeToWaitBeforeMarkingUnreachableAfterSettingUpSubscription 10 + // Consider moving utility classes to their own file #pragma mark - Utility Classes // This class is for storing weak references in a container @@ -125,6 +127,12 @@ - (id)strongObject } // anonymous namespace #pragma mark - MTRDevice +typedef NS_ENUM(NSUInteger, MTRInternalDeviceState) { + MTRInternalDeviceStateUnsubscribed = 0, + MTRInternalDeviceStateSubscribing = 1, + MTRInternalDeviceStateSubscribed = 2 +}; + typedef NS_ENUM(NSUInteger, MTRDeviceExpectedValueFieldIndex) { MTRDeviceExpectedValueFieldExpirationTimeIndex = 0, MTRDeviceExpectedValueFieldValueIndex = 1, @@ -157,18 +165,10 @@ @interface MTRDevice () @property (nonatomic) BOOL receivingPrimingReport; // TODO: instead of all the BOOL properties that are some facet of the state, move to internal state machine that has (at least): -// Unsubscribed (not attemping) -// Attempting subscription -// Subscribed (gotten subscription response / in steady state with no OnError/OnDone) // Actively receiving report // Actively receiving priming report -/** - * If subscriptionActive is true that means that either we are in the middle of - * trying to get a CASE session for the publisher or we have a live ReadClient - * right now (possibly with a lost subscription and trying to re-subscribe). - */ -@property (nonatomic) BOOL subscriptionActive; +@property (nonatomic) MTRInternalDeviceState internalDeviceState; #define MTRDEVICE_SUBSCRIPTION_ATTEMPT_MIN_WAIT_SECONDS (1) #define MTRDEVICE_SUBSCRIPTION_ATTEMPT_MAX_WAIT_SECONDS (3600) @@ -640,6 +640,7 @@ - (void)_handleSubscriptionEstablished // reset subscription attempt wait time when subscription succeeds _lastSubscriptionAttemptWait = 0; + _internalDeviceState = MTRInternalDeviceStateSubscribed; // As subscription is established, check if the delegate needs to be informed if (!_delegateDeviceCachePrimedCalled) { @@ -663,7 +664,7 @@ - (void)_handleSubscriptionError:(NSError *)error { os_unfair_lock_lock(&self->_lock); - _subscriptionActive = NO; + _internalDeviceState = MTRInternalDeviceStateUnsubscribed; _unreportedEvents = nil; [self _changeState:MTRDeviceStateUnreachable]; @@ -757,6 +758,17 @@ - (void)_handleUnsolicitedMessageFromPublisher os_unfair_lock_unlock(&self->_lock); } +- (void)_markDeviceAsUnreachableIfNotSusbcribed +{ + os_unfair_lock_assert_owner(&self->_lock); + + if (_internalDeviceState >= MTRInternalDeviceStateSubscribed) + return; + + MTR_LOG_DEFAULT("%@ still not subscribed, marking the device as unreachable", self); + [self _changeState:MTRDeviceStateUnreachable]; +} + - (void)_handleReportBegin { os_unfair_lock_lock(&self->_lock); @@ -991,11 +1003,20 @@ - (void)_setupSubscription #endif // for now just subscribe once - if (_subscriptionActive) { + if (_internalDeviceState > MTRInternalDeviceStateUnsubscribed) { return; } - _subscriptionActive = YES; + _internalDeviceState = MTRInternalDeviceStateSubscribing; + + // Set up a timer to mark as not reachable if it takes too long to set up a subscription + MTRWeakReference * weakSelf = [MTRWeakReference weakReferenceWithObject:self]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t) (kTimeToWaitBeforeMarkingUnreachableAfterSettingUpSubscription * NSEC_PER_SEC)), self.queue, ^{ + MTRDevice * strongSelf = weakSelf.strongObject; + os_unfair_lock_lock(&strongSelf->_lock); + [strongSelf _markDeviceAsUnreachableIfNotSusbcribed]; + os_unfair_lock_unlock(&strongSelf->_lock); + }); [_deviceController getSessionForNode:_nodeID.unsignedLongLongValue From 320c1f098bfd68b0fdd834c7de741c79040efbf9 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 14 Mar 2024 20:38:19 -0400 Subject: [PATCH 07/27] MTR_EXTERN should not imply MTR_EXPORT. (#32580) Most things using MTR_EXTERN had availability annotations anyway, which imply MTR_EXPORT. --- src/darwin/Framework/CHIP/MTRDefines.h | 4 ++-- .../Framework/CHIP/MTRDeviceControllerLocalTestStorage.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDefines.h b/src/darwin/Framework/CHIP/MTRDefines.h index fea32d4a0e076e..fb8fed4f678ab8 100644 --- a/src/darwin/Framework/CHIP/MTRDefines.h +++ b/src/darwin/Framework/CHIP/MTRDefines.h @@ -51,9 +51,9 @@ #define MTR_EXPORT __attribute__((visibility("default"))) #ifdef __cplusplus -#define MTR_EXTERN extern "C" MTR_EXPORT +#define MTR_EXTERN extern "C" #else -#define MTR_EXTERN extern MTR_EXPORT +#define MTR_EXTERN extern #endif #if __has_attribute(__swift_attr__) diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerLocalTestStorage.h b/src/darwin/Framework/CHIP/MTRDeviceControllerLocalTestStorage.h index 915834ecabd35c..92670f1721c3e2 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerLocalTestStorage.h +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerLocalTestStorage.h @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN -MTR_EXTERN @interface MTRDeviceControllerLocalTestStorage : NSObject +MTR_EXTERN MTR_EXPORT @interface MTRDeviceControllerLocalTestStorage : NSObject // Setting this variable only affects subsequent MTRDeviceController initializations @property (class, nonatomic, assign) BOOL localTestStorageEnabled; From 22901a1996f636c5825825bb3d0e7376a8c201f1 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 14 Mar 2024 21:45:56 -0400 Subject: [PATCH 08/27] Update ZAP to v2024.03.14-nightly. (#32564) This fixes the endpoint_config representation of "endpoint with no specific parent endpoint set" to match the SDK internal representation, so we can remove some code that was working around the representation mismatch. --- scripts/setup/zap.json | 4 ++-- scripts/setup/zap.version | 2 +- .../app-templates/endpoint_config.h | 2 +- .../lighting-app/app-templates/endpoint_config.h | 2 +- scripts/tools/zap/zap_execution.py | 2 +- src/app/util/attribute-storage.cpp | 13 +++---------- 6 files changed, 9 insertions(+), 16 deletions(-) diff --git a/scripts/setup/zap.json b/scripts/setup/zap.json index 1945fece82ed1a..173a68633f6d0e 100644 --- a/scripts/setup/zap.json +++ b/scripts/setup/zap.json @@ -8,13 +8,13 @@ "mac-amd64", "windows-amd64" ], - "tags": ["version:2@v2024.02.29-nightly.1"] + "tags": ["version:2@v2024.03.14-nightly.1"] }, { "_comment": "Always get the amd64 version on mac until usable arm64 zap build is available", "path": "fuchsia/third_party/zap/mac-amd64", "platforms": ["mac-arm64"], - "tags": ["version:2@v2024.02.29-nightly.1"] + "tags": ["version:2@v2024.03.14-nightly.1"] } ] } diff --git a/scripts/setup/zap.version b/scripts/setup/zap.version index 4a877a4050f7e2..6f551d6ac92803 100644 --- a/scripts/setup/zap.version +++ b/scripts/setup/zap.version @@ -1 +1 @@ -v2024.02.29-nightly +v2024.03.14-nightly diff --git a/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/endpoint_config.h b/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/endpoint_config.h index 09b17447be9e52..ca2e15eedf5683 100644 --- a/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/endpoint_config.h +++ b/scripts/tools/zap/tests/outputs/all-clusters-app/app-templates/endpoint_config.h @@ -3072,5 +3072,5 @@ static_assert(ATTRIBUTE_LARGEST <= CHIP_CONFIG_MAX_ATTRIBUTE_STORE_ELEMENT_SIZE, // Array of parent endpoints for each endpoint #define FIXED_PARENT_ENDPOINTS \ { \ - 0, 0, 0, 0 \ + kInvalidEndpointId, kInvalidEndpointId, kInvalidEndpointId, kInvalidEndpointId \ } diff --git a/scripts/tools/zap/tests/outputs/lighting-app/app-templates/endpoint_config.h b/scripts/tools/zap/tests/outputs/lighting-app/app-templates/endpoint_config.h index 0e6ba62431b71e..f04deadd1610b7 100644 --- a/scripts/tools/zap/tests/outputs/lighting-app/app-templates/endpoint_config.h +++ b/scripts/tools/zap/tests/outputs/lighting-app/app-templates/endpoint_config.h @@ -1210,5 +1210,5 @@ static_assert(ATTRIBUTE_LARGEST <= CHIP_CONFIG_MAX_ATTRIBUTE_STORE_ELEMENT_SIZE, // Array of parent endpoints for each endpoint #define FIXED_PARENT_ENDPOINTS \ { \ - 0, 0 \ + kInvalidEndpointId, kInvalidEndpointId \ } diff --git a/scripts/tools/zap/zap_execution.py b/scripts/tools/zap/zap_execution.py index fafe3e5e29d2d9..b5880f761dd41d 100644 --- a/scripts/tools/zap/zap_execution.py +++ b/scripts/tools/zap/zap_execution.py @@ -23,7 +23,7 @@ # Use scripts/tools/zap/version_update.py to manage ZAP versioning as many # files may need updating for versions # -MIN_ZAP_VERSION = '2024.2.29' +MIN_ZAP_VERSION = '2024.3.14' class ZapTool: diff --git a/src/app/util/attribute-storage.cpp b/src/app/util/attribute-storage.cpp index 3bce63e6da254c..62e4631297e7d0 100644 --- a/src/app/util/attribute-storage.cpp +++ b/src/app/util/attribute-storage.cpp @@ -214,16 +214,9 @@ void emberAfEndpointConfigure() emAfEndpoints[ep].endpoint = fixedEndpoints[ep]; emAfEndpoints[ep].deviceTypeList = Span(&fixedDeviceTypeList[fixedDeviceTypeListOffsets[ep]], fixedDeviceTypeListLengths[ep]); - emAfEndpoints[ep].endpointType = &generatedEmberAfEndpointTypes[fixedEmberAfEndpointTypes[ep]]; - emAfEndpoints[ep].dataVersions = currentDataVersions; - if (fixedParentEndpoints[ep] == 0) - { - emAfEndpoints[ep].parentEndpointId = kInvalidEndpointId; - } - else - { - emAfEndpoints[ep].parentEndpointId = fixedParentEndpoints[ep]; - } + emAfEndpoints[ep].endpointType = &generatedEmberAfEndpointTypes[fixedEmberAfEndpointTypes[ep]]; + emAfEndpoints[ep].dataVersions = currentDataVersions; + emAfEndpoints[ep].parentEndpointId = fixedParentEndpoints[ep]; emAfEndpoints[ep].bitmask.Set(EmberAfEndpointOptions::isEnabled); emAfEndpoints[ep].bitmask.Set(EmberAfEndpointOptions::isFlatComposition); From 09fbf5cc1bfc1062aaf752cb7a9df622ca42f188 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 14 Mar 2024 22:06:53 -0400 Subject: [PATCH 09/27] Stop forcing a particular interface ID when sending response messages. (#32581) Because we were storing the PeerAddress in the session when getting a message, we effectively pinned sessions to particular interface ids at that point. This can lead to routing failures. We should only be pinning to interface IDs for link-local addresses, just like we do for initial IP resolution via DNS-SD. --- src/transport/SessionManager.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/transport/SessionManager.cpp b/src/transport/SessionManager.cpp index 085fda948629c7..78f9d98d79600f 100644 --- a/src/transport/SessionManager.cpp +++ b/src/transport/SessionManager.cpp @@ -57,6 +57,20 @@ using Transport::SecureSession; namespace { Global gGroupPeerTable; + +// Helper function that strips off the interface ID from a peer address that is +// not an IPv6 link-local address. For any other address type we should rely on +// the device's routing table to route messages sent. Forcing messages down a +// specific interface might fail with "no route to host". +void CorrectPeerAddressInterfaceID(Transport::PeerAddress & peerAddress) +{ + if (peerAddress.GetIPAddress().IsIPv6LinkLocal()) + { + return; + } + peerAddress.SetInterface(Inet::InterfaceId::Null()); +} + } // namespace uint32_t EncryptedPacketBufferHandle::GetMessageCounter() const @@ -633,7 +647,9 @@ void SessionManager::UnauthenticatedMessageDispatch(const PacketHeader & partial const SessionHandle & session = optionalSession.Value(); Transport::UnauthenticatedSession * unsecuredSession = session->AsUnauthenticatedSession(); - unsecuredSession->SetPeerAddress(peerAddress); + Transport::PeerAddress mutablePeerAddress = peerAddress; + CorrectPeerAddressInterfaceID(mutablePeerAddress); + unsecuredSession->SetPeerAddress(mutablePeerAddress); SessionMessageDelegate::DuplicateMessage isDuplicate = SessionMessageDelegate::DuplicateMessage::No; unsecuredSession->MarkActiveRx(); @@ -766,12 +782,11 @@ void SessionManager::SecureUnicastMessageDispatch(const PacketHeader & partialPa secureSession->GetSessionMessageCounter().GetPeerMessageCounter().CommitEncryptedUnicast(packetHeader.GetMessageCounter()); } - // TODO: once mDNS address resolution is available reconsider if this is required - // This updates the peer address once a packet is received from a new address - // and serves as a way to auto-detect peer changing IPs. - if (secureSession->GetPeerAddress() != peerAddress) + Transport::PeerAddress mutablePeerAddress = peerAddress; + CorrectPeerAddressInterfaceID(mutablePeerAddress); + if (secureSession->GetPeerAddress() != mutablePeerAddress) { - secureSession->SetPeerAddress(peerAddress); + secureSession->SetPeerAddress(mutablePeerAddress); } if (mCB != nullptr) From f0b9945191468dac7dfd47ffa66ae27368635664 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 14 Mar 2024 22:09:02 -0400 Subject: [PATCH 10/27] Remove redundant checks from the operational state impl in all-clusters-app. (#32573) The cluster implementation does those checks. --- .../src/operational-state-delegate-impl.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp index 2070e1b86754fe..d258b8261a1aed 100644 --- a/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp +++ b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp @@ -55,15 +55,6 @@ CHIP_ERROR GenericOperationalStateDelegateImpl::GetOperationalPhaseAtIndex(size_ void GenericOperationalStateDelegateImpl::HandlePauseStateCallback(GenericOperationalError & err) { - OperationalState::OperationalStateEnum state = - static_cast(GetInstance()->GetCurrentOperationalState()); - - if (state == OperationalState::OperationalStateEnum::kStopped || state == OperationalState::OperationalStateEnum::kError) - { - err.Set(to_underlying(OperationalState::ErrorStateEnum::kCommandInvalidInState)); - return; - } - // placeholder implementation auto error = GetInstance()->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kPaused)); if (error == CHIP_NO_ERROR) @@ -78,15 +69,6 @@ void GenericOperationalStateDelegateImpl::HandlePauseStateCallback(GenericOperat void GenericOperationalStateDelegateImpl::HandleResumeStateCallback(GenericOperationalError & err) { - OperationalState::OperationalStateEnum state = - static_cast(GetInstance()->GetCurrentOperationalState()); - - if (state == OperationalState::OperationalStateEnum::kStopped || state == OperationalState::OperationalStateEnum::kError) - { - err.Set(to_underlying(OperationalState::ErrorStateEnum::kCommandInvalidInState)); - return; - } - // placeholder implementation auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning)); if (error == CHIP_NO_ERROR) From 2e13ddccd8d69b6fe22f5aac2dbe1205833a7ed9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Bokowy Date: Fri, 15 Mar 2024 03:23:16 +0100 Subject: [PATCH 11/27] [Python] Delete BLE scanner handle in an explicit way (#32574) * Normalize BT MAC letter case passed to DiscoverAsync * Remove obsolete docstrings * [Python] Delete BLE scanner handle in an explicit way * Update callback names * Get rid of obsolete comments * Unref manager client on glib thread --- src/controller/python/chip/ble/LinuxImpl.cpp | 18 ++- .../python/chip/ble/darwin/Scanning.mm | 7 +- .../python/chip/ble/library_handle.py | 6 +- .../python/chip/ble/scan_devices.py | 143 +++++++++--------- src/platform/Linux/bluez/AdapterIterator.cpp | 12 ++ src/platform/Linux/bluez/AdapterIterator.h | 5 + .../Linux/bluez/ChipDeviceScanner.cpp | 30 ++-- 7 files changed, 119 insertions(+), 102 deletions(-) diff --git a/src/controller/python/chip/ble/LinuxImpl.cpp b/src/controller/python/chip/ble/LinuxImpl.cpp index 6218d9c66d4c83..852e1f725d22b0 100644 --- a/src/controller/python/chip/ble/LinuxImpl.cpp +++ b/src/controller/python/chip/ble/LinuxImpl.cpp @@ -111,11 +111,9 @@ class ScannerDelegateImpl : public ChipDeviceScannerDelegate { mCompleteCallback(mContext); } - - delete this; } - virtual void OnScanError(CHIP_ERROR error) override + void OnScanError(CHIP_ERROR error) override { if (mErrorCallback) { @@ -133,10 +131,10 @@ class ScannerDelegateImpl : public ChipDeviceScannerDelegate } // namespace -extern "C" void * pychip_ble_start_scanning(PyObject * context, void * adapter, uint32_t timeoutMs, - ScannerDelegateImpl::DeviceScannedCallback scanCallback, - ScannerDelegateImpl::ScanCompleteCallback completeCallback, - ScannerDelegateImpl::ScanErrorCallback errorCallback) +extern "C" void * pychip_ble_scanner_start(PyObject * context, void * adapter, uint32_t timeoutMs, + ScannerDelegateImpl::DeviceScannedCallback scanCallback, + ScannerDelegateImpl::ScanCompleteCallback completeCallback, + ScannerDelegateImpl::ScanErrorCallback errorCallback) { std::unique_ptr delegate = std::make_unique(context, scanCallback, completeCallback, errorCallback); @@ -151,3 +149,9 @@ extern "C" void * pychip_ble_start_scanning(PyObject * context, void * adapter, return delegate.release(); } + +extern "C" void pychip_ble_scanner_delete(void * scanner) +{ + chip::DeviceLayer::StackLock lock; + delete static_cast(scanner); +} diff --git a/src/controller/python/chip/ble/darwin/Scanning.mm b/src/controller/python/chip/ble/darwin/Scanning.mm index caf18c658ebc9c..d3fb928790ab3e 100644 --- a/src/controller/python/chip/ble/darwin/Scanning.mm +++ b/src/controller/python/chip/ble/darwin/Scanning.mm @@ -131,7 +131,7 @@ - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPerip @end -extern "C" void * pychip_ble_start_scanning(PyObject * context, void * adapter, uint32_t timeout, +extern "C" void * pychip_ble_scanner_start(PyObject * context, void * adapter, uint32_t timeout, DeviceScannedCallback scanCallback, ScanCompleteCallback completeCallback, ScanErrorCallback errorCallback) { // NOTE: adapter is ignored as it does not apply to mac @@ -144,3 +144,8 @@ - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPerip return (__bridge_retained void *) (scanner); } + +extern "C" void pychip_ble_scanner_delete(void * scanner) +{ + CFRelease((CFTypeRef) scanner); +} diff --git a/src/controller/python/chip/ble/library_handle.py b/src/controller/python/chip/ble/library_handle.py index 2dbb52f28bf429..00f2a77eab68d6 100644 --- a/src/controller/python/chip/ble/library_handle.py +++ b/src/controller/python/chip/ble/library_handle.py @@ -57,8 +57,10 @@ def _GetBleLibraryHandle() -> ctypes.CDLL: setter.Set('pychip_ble_adapter_list_get_raw_adapter', VoidPointer, [VoidPointer]) - setter.Set('pychip_ble_start_scanning', VoidPointer, [ - py_object, VoidPointer, c_uint32, DeviceScannedCallback, ScanDoneCallback, ScanErrorCallback + setter.Set('pychip_ble_scanner_start', VoidPointer, [ + py_object, VoidPointer, c_uint32, DeviceScannedCallback, + ScanDoneCallback, ScanErrorCallback, ]) + setter.Set('pychip_ble_scanner_delete', None, [VoidPointer]) return handle diff --git a/src/controller/python/chip/ble/scan_devices.py b/src/controller/python/chip/ble/scan_devices.py index 5e9ee24dadd1c4..86cc0a321e8c7d 100644 --- a/src/controller/python/chip/ble/scan_devices.py +++ b/src/controller/python/chip/ble/scan_devices.py @@ -17,6 +17,7 @@ import ctypes from dataclasses import dataclass from queue import Queue +from threading import Thread from typing import Generator from chip.ble.library_handle import _GetBleLibraryHandle @@ -26,75 +27,17 @@ @DeviceScannedCallback def ScanFoundCallback(closure, address: str, discriminator: int, vendor: int, product: int): - closure.DeviceFound(address, discriminator, vendor, product) + closure.OnDeviceScanned(address, discriminator, vendor, product) @ScanDoneCallback def ScanDoneCallback(closure): - closure.ScanCompleted() + closure.OnScanComplete() @ScanErrorCallback def ScanErrorCallback(closure, errorCode: int): - closure.ScanErrorCallback(errorCode) - - -def DiscoverAsync(timeoutMs: int, scanCallback, doneCallback, errorCallback, adapter=None): - """Initiate a BLE discovery of devices with the given timeout. - - NOTE: devices are not guaranteed to be unique. New entries are returned - as soon as the underlying BLE manager detects changes. - - Args: - timeoutMs: scan will complete after this time - scanCallback: callback when a device is found - doneCallback: callback when the scan is complete - errorCallback: callback when error occurred during scan - adapter: what adapter to choose. Either an AdapterInfo object or - a string with the adapter address. If None, the first - adapter on the system is used. - """ - if adapter and not isinstance(adapter, str): - adapter = adapter.address - - handle = _GetBleLibraryHandle() - - nativeList = handle.pychip_ble_adapter_list_new() - if nativeList == 0: - raise Exception('Failed to list available adapters') - - try: - while handle.pychip_ble_adapter_list_next(nativeList): - if adapter and (adapter != handle.pychip_ble_adapter_list_get_address( - nativeList).decode('utf8')): - continue - - class ScannerClosure: - - def DeviceFound(self, *args): - scanCallback(*args) - - def ScanCompleted(self, *args): - doneCallback(*args) - ctypes.pythonapi.Py_DecRef(ctypes.py_object(self)) - - def ScanErrorCallback(self, *args): - errorCallback(*args) - - closure = ScannerClosure() - ctypes.pythonapi.Py_IncRef(ctypes.py_object(closure)) - - scanner = handle.pychip_ble_start_scanning( - ctypes.py_object(closure), - handle.pychip_ble_adapter_list_get_raw_adapter( - nativeList), timeoutMs, - ScanFoundCallback, ScanDoneCallback, ScanErrorCallback) - - if scanner == 0: - raise Exception('Failed to initiate scan') - break - finally: - handle.pychip_ble_adapter_list_delete(nativeList) + closure.OnScanError(errorCode) @dataclass @@ -107,7 +50,7 @@ class DeviceInfo: class _DeviceInfoReceiver: """Uses a queue to notify of objects received asynchronously - from a ble scan. + from a BLE scan. Internal queue gets filled on DeviceFound and ends with None when ScanCompleted. @@ -116,13 +59,13 @@ class _DeviceInfoReceiver: def __init__(self): self.queue = Queue() - def DeviceFound(self, address, discriminator, vendor, product): + def OnDeviceScanned(self, address, discriminator, vendor, product): self.queue.put(DeviceInfo(address, discriminator, vendor, product)) - def ScanCompleted(self): + def OnScanComplete(self): self.queue.put(None) - def ScanError(self, errorCode): + def OnScanError(self, errorCode): # TODO need to determine what we do with this error. Most of the time this # error is just a timeout introduced in PR #24873, right before we get a # ScanCompleted. @@ -132,24 +75,76 @@ def ScanError(self, errorCode): def DiscoverSync(timeoutMs: int, adapter=None) -> Generator[DeviceInfo, None, None]: """Discover BLE devices over the specified period of time. - NOTE: devices are not guaranteed to be unique. New entries are returned + NOTE: Devices are not guaranteed to be unique. New entries are returned + as soon as the underlying BLE manager detects changes. + + Args: + timeoutMs: scan will complete after this time + adapter: what adapter to choose. Either an AdapterInfo object or + a string with the adapter address. If None, the first + adapter on the system is used. + """ + if adapter: + if isinstance(adapter, str): + adapter = adapter.upper() + else: + adapter = adapter.address + + handle = _GetBleLibraryHandle() + + nativeList = handle.pychip_ble_adapter_list_new() + if nativeList == 0: + raise Exception('Failed to list available adapters') + + try: + while handle.pychip_ble_adapter_list_next(nativeList): + if adapter and (adapter != handle.pychip_ble_adapter_list_get_address( + nativeList).decode('utf8')): + continue + + receiver = _DeviceInfoReceiver() + scanner = handle.pychip_ble_scanner_start( + ctypes.py_object(receiver), + handle.pychip_ble_adapter_list_get_raw_adapter(nativeList), + timeoutMs, ScanFoundCallback, ScanDoneCallback, ScanErrorCallback) + + if scanner == 0: + raise Exception('Failed to start BLE scan') + + while True: + data = receiver.queue.get() + if not data: + break + yield data + + handle.pychip_ble_scanner_delete(scanner) + break + finally: + handle.pychip_ble_adapter_list_delete(nativeList) + + +def DiscoverAsync(timeoutMs: int, scanCallback, doneCallback, errorCallback, adapter=None): + """Discover BLE devices over the specified period of time without blocking. + + NOTE: Devices are not guaranteed to be unique. The scanCallback is called as soon as the underlying BLE manager detects changes. Args: timeoutMs: scan will complete after this time scanCallback: callback when a device is found doneCallback: callback when the scan is complete + errorCallback: callback when error occurred during scan adapter: what adapter to choose. Either an AdapterInfo object or a string with the adapter address. If None, the first adapter on the system is used. """ - receiver = _DeviceInfoReceiver() - DiscoverAsync(timeoutMs, receiver.DeviceFound, - receiver.ScanCompleted, receiver.ScanError, adapter) + def _DiscoverAsync(timeoutMs, scanCallback, doneCallback, errorCallback, adapter): + for device in DiscoverSync(timeoutMs, adapter): + scanCallback(device.address, device.discriminator, device.vendor, device.product) + doneCallback() - while True: - data = receiver.queue.get() - if not data: - break - yield data + t = Thread(target=_DiscoverAsync, + args=(timeoutMs, scanCallback, doneCallback, errorCallback, adapter), + daemon=True) + t.start() diff --git a/src/platform/Linux/bluez/AdapterIterator.cpp b/src/platform/Linux/bluez/AdapterIterator.cpp index ced172446882b8..0455359f424bfb 100644 --- a/src/platform/Linux/bluez/AdapterIterator.cpp +++ b/src/platform/Linux/bluez/AdapterIterator.cpp @@ -47,6 +47,18 @@ CHIP_ERROR AdapterIterator::Initialize() return CHIP_NO_ERROR; } +CHIP_ERROR AdapterIterator::Shutdown() +{ + // Release resources on the glib thread to synchronize with potential signal handlers + // attached to the manager client object that may run on the glib thread. + return PlatformMgrImpl().GLibMatterContextInvokeSync( + +[](AdapterIterator * self) { + self->mManager.reset(); + return CHIP_NO_ERROR; + }, + this); +} + bool AdapterIterator::Advance() { for (; mIterator != BluezObjectList::end(); ++mIterator) diff --git a/src/platform/Linux/bluez/AdapterIterator.h b/src/platform/Linux/bluez/AdapterIterator.h index 38af64f7ecc21c..c998df602f81fc 100644 --- a/src/platform/Linux/bluez/AdapterIterator.h +++ b/src/platform/Linux/bluez/AdapterIterator.h @@ -48,6 +48,9 @@ namespace Internal { class AdapterIterator { public: + AdapterIterator() = default; + ~AdapterIterator() { Shutdown(); } + /// Moves to the next DBUS interface. /// /// MUST be called before any of the 'current value' methods are @@ -66,6 +69,8 @@ class AdapterIterator private: /// Sets up the DBUS manager and loads the list CHIP_ERROR Initialize(); + /// Destroys the DBUS manager + CHIP_ERROR Shutdown(); /// Loads the next value in the list. /// diff --git a/src/platform/Linux/bluez/ChipDeviceScanner.cpp b/src/platform/Linux/bluez/ChipDeviceScanner.cpp index 5523bf87dfdf66..ce52b8482752b8 100644 --- a/src/platform/Linux/bluez/ChipDeviceScanner.cpp +++ b/src/platform/Linux/bluez/ChipDeviceScanner.cpp @@ -112,16 +112,12 @@ CHIP_ERROR ChipDeviceScanner::StartScan(System::Clock::Timeout timeout) VerifyOrReturnError(mTimerState == ScannerTimerState::TIMER_CANCELED, CHIP_ERROR_INCORRECT_STATE); mCancellable.reset(g_cancellable_new()); - if (PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStartScan, this) != CHIP_NO_ERROR) + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStartScan, this); + if (err != CHIP_NO_ERROR) { - ChipLogError(Ble, "Failed to schedule BLE scan start."); - - ChipDeviceScannerDelegate * delegate = this->mDelegate; - // callback is explicitly allowed to delete the scanner (hence no more - // references to 'self' here) - delegate->OnScanComplete(); - - return CHIP_ERROR_INTERNAL; + ChipLogError(Ble, "Failed to initiate BLE scan start: %" CHIP_ERROR_FORMAT, err.Format()); + mDelegate->OnScanComplete(); + return err; } // Here need to set the Bluetooth scanning status immediately. @@ -129,14 +125,14 @@ CHIP_ERROR ChipDeviceScanner::StartScan(System::Clock::Timeout timeout) // calling StopScan will be effective. mScannerState = ChipDeviceScannerState::SCANNER_SCANNING; - CHIP_ERROR err = chip::DeviceLayer::SystemLayer().StartTimer(timeout, TimerExpiredCallback, static_cast(this)); - + err = chip::DeviceLayer::SystemLayer().StartTimer(timeout, TimerExpiredCallback, static_cast(this)); if (err != CHIP_NO_ERROR) { - ChipLogError(Ble, "Failed to schedule scan timeout."); + ChipLogError(Ble, "Failed to schedule scan timeout: %" CHIP_ERROR_FORMAT, err.Format()); StopScan(); return err; } + mTimerState = ScannerTimerState::TIMER_STARTED; ChipLogDetail(Ble, "ChipDeviceScanner has started scanning!"); @@ -157,9 +153,10 @@ CHIP_ERROR ChipDeviceScanner::StopScan() assertChipStackLockedByCurrentThread(); VerifyOrReturnError(mScannerState == ChipDeviceScannerState::SCANNER_SCANNING, CHIP_NO_ERROR); - if (PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStopScan, this) != CHIP_NO_ERROR) + CHIP_ERROR err = PlatformMgrImpl().GLibMatterContextInvokeSync(MainLoopStopScan, this); + if (err != CHIP_NO_ERROR) { - ChipLogError(Ble, "Failed to schedule BLE scan stop."); + ChipLogError(Ble, "Failed to initiate BLE scan stop: %" CHIP_ERROR_FORMAT, err.Format()); return CHIP_ERROR_INTERNAL; } @@ -176,10 +173,7 @@ CHIP_ERROR ChipDeviceScanner::StopScan() // Reset timer status mTimerState = ScannerTimerState::TIMER_CANCELED; - ChipDeviceScannerDelegate * delegate = this->mDelegate; - // callback is explicitly allowed to delete the scanner (hence no more - // references to 'self' here) - delegate->OnScanComplete(); + mDelegate->OnScanComplete(); return CHIP_NO_ERROR; } From 73d3147ba8bae560edda38e72d332595d9fd2b4c Mon Sep 17 00:00:00 2001 From: joonhaengHeo <85541460+joonhaengHeo@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:23:34 +0900 Subject: [PATCH 12/27] [Android] Add StopDevicePairing method in Java (#32566) --- .../chip/devicecontroller/ChipDeviceController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index 25d300ec1cadc1..d6b31c59dabcd5 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -453,6 +453,15 @@ public void unpairDeviceCallback(long deviceId, UnpairDeviceCallback callback) { unpairDeviceCallback(deviceControllerPtr, deviceId, callback); } + /** + * This function stops a pairing or commissioning process that is in progress. + * + * @param deviceId The remote device Id. + */ + public void stopDevicePairing(long deviceId) { + stopDevicePairing(deviceControllerPtr, deviceId); + } + /** * Returns a pointer to a device currently being commissioned. This should be used before the * device is operationally available. @@ -1474,6 +1483,8 @@ private native void continueCommissioning( private native void unpairDeviceCallback( long deviceControllerPtr, long deviceId, UnpairDeviceCallback callback); + private native void stopDevicePairing(long deviceControllerPtr, long deviceId); + private native long getDeviceBeingCommissionedPointer(long deviceControllerPtr, long nodeId); private native void getConnectedDevicePointer( From dfab05d8dd1cc77b90b1568cc573360f8ca82b9c Mon Sep 17 00:00:00 2001 From: joonhaengHeo <85541460+joonhaengHeo@users.noreply.github.com> Date: Fri, 15 Mar 2024 11:23:59 +0900 Subject: [PATCH 13/27] [Android] Implement check in delegate (#32557) * Implement Android check in delegate * Restyled by whitespace * Restyled by google-java-format * Restyled by clang-format * Update documentation * Restyled by whitespace * Restyled by google-java-format * Fix jni object reference issue, add chiptool callback * Restyled by google-java-format * Restyled by clang-format * Update kotlin codestyle * remove public * fix typo * Restyled by clang-format * remove chip * Modify from comment --------- Co-authored-by: Restyled.io --- .../com/google/chip/chiptool/ChipClient.kt | 19 ++ .../java/AndroidCheckInDelegate.cpp | 162 ++++++++++++++++++ src/controller/java/AndroidCheckInDelegate.h | 52 ++++++ .../java/AndroidDeviceControllerWrapper.cpp | 5 + .../java/AndroidDeviceControllerWrapper.h | 6 +- src/controller/java/BUILD.gn | 4 + .../java/CHIPDeviceController-JNI.cpp | 19 ++ .../ChipDeviceController.java | 8 + .../devicecontroller/ICDCheckInDelegate.java | 55 ++++++ .../ICDCheckInDelegateWrapper.java | 59 +++++++ 10 files changed, 387 insertions(+), 2 deletions(-) create mode 100644 src/controller/java/AndroidCheckInDelegate.cpp create mode 100644 src/controller/java/AndroidCheckInDelegate.h create mode 100644 src/controller/java/src/chip/devicecontroller/ICDCheckInDelegate.java create mode 100644 src/controller/java/src/chip/devicecontroller/ICDCheckInDelegateWrapper.java diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt index f8686ce3220b70..0534f7f3ee3589 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt @@ -22,6 +22,8 @@ import android.util.Log import chip.devicecontroller.ChipDeviceController import chip.devicecontroller.ControllerParams import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback +import chip.devicecontroller.ICDCheckInDelegate +import chip.devicecontroller.ICDClientInfo import chip.platform.AndroidBleManager import chip.platform.AndroidChipPlatform import chip.platform.ChipMdnsCallbackImpl @@ -60,6 +62,23 @@ object ChipClient { chipDeviceController.setAttestationTrustStoreDelegate( ExampleAttestationTrustStoreDelegate(chipDeviceController) ) + + chipDeviceController.setICDCheckInDelegate( + object : ICDCheckInDelegate { + override fun onCheckInComplete(info: ICDClientInfo) { + Log.d(TAG, "onCheckInComplete : $info") + } + + override fun onKeyRefreshNeeded(info: ICDClientInfo): ByteArray? { + Log.d(TAG, "onKeyRefreshNeeded : $info") + return null + } + + override fun onKeyRefreshDone(errorCode: Long) { + Log.d(TAG, "onKeyRefreshDone : $errorCode") + } + } + ) } return chipDeviceController diff --git a/src/controller/java/AndroidCheckInDelegate.cpp b/src/controller/java/AndroidCheckInDelegate.cpp new file mode 100644 index 00000000000000..1091738c804bbc --- /dev/null +++ b/src/controller/java/AndroidCheckInDelegate.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024 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. + */ + +#include "AndroidCheckInDelegate.h" + +#include +#include +#include +#include +#include + +#define PARSE_CLIENT_INFO(_clientInfo, _peerNodeId, _startCounter, _offset, _monitoredSubject, _jniICDAesKey, _jniICDHmacKey) \ + jlong _peerNodeId = static_cast(_clientInfo.peer_node.GetNodeId()); \ + jlong _startCounter = static_cast(_clientInfo.start_icd_counter); \ + jlong _offset = static_cast(_clientInfo.offset); \ + jlong _monitoredSubject = static_cast(_clientInfo.monitored_subject); \ + chip::ByteSpan aes_buf(_clientInfo.aes_key_handle.As()); \ + chip::ByteSpan hmac_buf(_clientInfo.hmac_key_handle.As()); \ + chip::ByteArray _jniICDAesKey(env, aes_buf); \ + chip::ByteArray _jniICDHmacKey(env, hmac_buf); + +namespace chip { +namespace app { + +CHIP_ERROR AndroidCheckInDelegate::Init(ICDClientStorage * storage, InteractionModelEngine * engine) +{ + VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mpStorage == nullptr, CHIP_ERROR_INCORRECT_STATE); + mpStorage = storage; + mpImEngine = engine; + return CHIP_NO_ERROR; +} + +CHIP_ERROR AndroidCheckInDelegate::SetDelegate(jobject checkInDelegateObj) +{ + ReturnLogErrorOnFailure(mCheckInDelegate.Init(checkInDelegateObj)); + return CHIP_NO_ERROR; +} + +void AndroidCheckInDelegate::OnCheckInComplete(const ICDClientInfo & clientInfo) +{ + ChipLogProgress( + ICD, "Check In Message processing complete: start_counter=%" PRIu32 " offset=%" PRIu32 " nodeid=" ChipLogFormatScopedNodeId, + clientInfo.start_icd_counter, clientInfo.offset, ChipLogValueScopedNodeId(clientInfo.peer_node)); + + VerifyOrReturn(mCheckInDelegate.HasValidObjectRef(), ChipLogProgress(ICD, "check-in delegate is not implemented!")); + + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Controller, "JNIEnv is null!")); + PARSE_CLIENT_INFO(clientInfo, peerNodeId, startCounter, offset, monitoredSubject, jniICDAesKey, jniICDHmacKey) + + jmethodID onCheckInCompleteMethodID = nullptr; + CHIP_ERROR err = chip::JniReferences::GetInstance().FindMethod(env, mCheckInDelegate.ObjectRef(), "onCheckInComplete", + "(JJJJ[B[B)V", &onCheckInCompleteMethodID); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogProgress(ICD, "onCheckInComplete - FindMethod is failed! : %" CHIP_ERROR_FORMAT, err.Format())); + + env->CallVoidMethod(mCheckInDelegate.ObjectRef(), onCheckInCompleteMethodID, peerNodeId, startCounter, offset, monitoredSubject, + jniICDAesKey.jniValue(), jniICDHmacKey.jniValue()); +} + +RefreshKeySender * AndroidCheckInDelegate::OnKeyRefreshNeeded(ICDClientInfo & clientInfo, ICDClientStorage * clientStorage) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + RefreshKeySender::RefreshKeyBuffer newKey; + + bool hasSetKey = false; + if (mCheckInDelegate.HasValidObjectRef()) + { + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(Controller, "JNIEnv is null!")); + + PARSE_CLIENT_INFO(clientInfo, peerNodeId, startCounter, offset, monitoredSubject, jniICDAesKey, jniICDHmacKey) + + jmethodID onKeyRefreshNeededMethodID = nullptr; + err = chip::JniReferences::GetInstance().FindMethod(env, mCheckInDelegate.ObjectRef(), "onKeyRefreshNeeded", "(JJJJ[B[B)V", + &onKeyRefreshNeededMethodID); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, + ChipLogProgress(ICD, "onKeyRefreshNeeded - FindMethod is failed! : %" CHIP_ERROR_FORMAT, err.Format())); + + jbyteArray key = static_cast(env->CallObjectMethod(mCheckInDelegate.ObjectRef(), onKeyRefreshNeededMethodID, + peerNodeId, startCounter, offset, monitoredSubject, + jniICDAesKey.jniValue(), jniICDHmacKey.jniValue())); + + if (key != nullptr) + { + JniByteArray jniKey(env, key); + VerifyOrReturnValue(static_cast(jniKey.size()) == newKey.Capacity(), nullptr, + ChipLogProgress(ICD, "Invalid key length : %d", jniKey.size())); + memcpy(newKey.Bytes(), jniKey.data(), newKey.Capacity()); + hasSetKey = true; + } + } + else + { + ChipLogProgress(ICD, "check-in delegate is not implemented!"); + } + if (!hasSetKey) + { + err = Crypto::DRBG_get_bytes(newKey.Bytes(), newKey.Capacity()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(ICD, "Generation of new key failed: %" CHIP_ERROR_FORMAT, err.Format()); + return nullptr; + } + } + + auto refreshKeySender = Platform::New(this, clientInfo, clientStorage, mpImEngine, newKey); + if (refreshKeySender == nullptr) + { + return nullptr; + } + return refreshKeySender; +} + +void AndroidCheckInDelegate::OnKeyRefreshDone(RefreshKeySender * refreshKeySender, CHIP_ERROR error) +{ + if (error == CHIP_NO_ERROR) + { + ChipLogProgress(ICD, "Re-registration with new key completed successfully"); + } + else + { + ChipLogError(ICD, "Re-registration with new key failed with error : %" CHIP_ERROR_FORMAT, error.Format()); + // The callee can take corrective action based on the error received. + } + + VerifyOrReturn(mCheckInDelegate.HasValidObjectRef(), ChipLogProgress(ICD, "check-in delegate is not implemented!")); + + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Controller, "JNIEnv is null!")); + + jmethodID onKeyRefreshDoneMethodID = nullptr; + CHIP_ERROR err = chip::JniReferences::GetInstance().FindMethod(env, mCheckInDelegate.ObjectRef(), "onKeyRefreshDone", "(J)V", + &onKeyRefreshDoneMethodID); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogProgress(ICD, "onKeyRefreshDone - FindMethod is failed! : %" CHIP_ERROR_FORMAT, err.Format())); + + env->CallVoidMethod(mCheckInDelegate.ObjectRef(), onKeyRefreshDoneMethodID, static_cast(error.AsInteger())); + + if (refreshKeySender != nullptr) + { + Platform::Delete(refreshKeySender); + refreshKeySender = nullptr; + } +} +} // namespace app +} // namespace chip diff --git a/src/controller/java/AndroidCheckInDelegate.h b/src/controller/java/AndroidCheckInDelegate.h new file mode 100644 index 00000000000000..5616b2815ed9b2 --- /dev/null +++ b/src/controller/java/AndroidCheckInDelegate.h @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2024 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. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { + +using namespace std; + +class InteractionModelEngine; + +/// Callbacks for check in protocol +class AndroidCheckInDelegate : public CheckInDelegate +{ +public: + virtual ~AndroidCheckInDelegate() {} + CHIP_ERROR Init(ICDClientStorage * storage, InteractionModelEngine * engine); + void OnCheckInComplete(const ICDClientInfo & clientInfo) override; + RefreshKeySender * OnKeyRefreshNeeded(ICDClientInfo & clientInfo, ICDClientStorage * clientStorage) override; + void OnKeyRefreshDone(RefreshKeySender * refreshKeySender, CHIP_ERROR error) override; + + CHIP_ERROR SetDelegate(jobject checkInDelegateObj); + +private: + ICDClientStorage * mpStorage = nullptr; + InteractionModelEngine * mpImEngine = nullptr; + + JniGlobalReference mCheckInDelegate; +}; + +} // namespace app +} // namespace chip diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index 0143d36e8d3c8e..d914bb5279f99e 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -689,6 +689,11 @@ CHIP_ERROR AndroidDeviceControllerWrapper::FinishOTAProvider() #endif } +CHIP_ERROR AndroidDeviceControllerWrapper::SetICDCheckInDelegate(jobject checkInDelegate) +{ + return mCheckInDelegate.SetDelegate(checkInDelegate); +} + void AndroidDeviceControllerWrapper::OnStatusUpdate(chip::Controller::DevicePairingDelegate::Status status) { chip::DeviceLayer::StackUnlock unlock; diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index fe56c02646942c..cc0152cdc66421 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -25,7 +25,6 @@ #include #include -#include #include #include #include @@ -43,6 +42,7 @@ #include #endif // JAVA_MATTER_CONTROLLER_TEST +#include "AndroidCheckInDelegate.h" #include "AndroidOperationalCredentialsIssuer.h" #include "AttestationTrustStoreBridge.h" #include "DeviceAttestationDelegateBridge.h" @@ -212,6 +212,8 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel chip::app::DefaultICDClientStorage * getICDClientStorage() { return &mICDClientStorage; } + CHIP_ERROR SetICDCheckInDelegate(jobject checkInDelegate); + private: using ChipDeviceControllerPtr = std::unique_ptr; @@ -225,7 +227,7 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel chip::Crypto::RawKeySessionKeystore mSessionKeystore; chip::app::DefaultICDClientStorage mICDClientStorage; - chip::app::DefaultCheckInDelegate mCheckInDelegate; + chip::app::AndroidCheckInDelegate mCheckInDelegate; chip::app::CheckInHandler mCheckInHandler; JavaVM * mJavaVM = nullptr; diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index ff42d1b43a42f4..9e135aa5de2f34 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -41,6 +41,8 @@ shared_library("jni") { "AndroidCallbacks-JNI.cpp", "AndroidCallbacks.cpp", "AndroidCallbacks.h", + "AndroidCheckInDelegate.cpp", + "AndroidCheckInDelegate.h", "AndroidClusterExceptions.cpp", "AndroidClusterExceptions.h", "AndroidCommissioningWindowOpener.cpp", @@ -459,6 +461,8 @@ android_library("java") { "src/chip/devicecontroller/ExtendableInvokeCallbackJni.java", "src/chip/devicecontroller/GetConnectedDeviceCallbackJni.java", "src/chip/devicecontroller/GroupKeySecurityPolicy.java", + "src/chip/devicecontroller/ICDCheckInDelegate.java", + "src/chip/devicecontroller/ICDCheckInDelegateWrapper.java", "src/chip/devicecontroller/ICDClientInfo.java", "src/chip/devicecontroller/ICDDeviceInfo.java", "src/chip/devicecontroller/ICDRegistrationInfo.java", diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 8890c881dc0c2c..f6509557650bee 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -588,6 +588,25 @@ JNI_METHOD(void, finishOTAProvider)(JNIEnv * env, jobject self, jlong handle) #endif } +JNI_METHOD(void, setICDCheckInDelegate)(JNIEnv * env, jobject self, jlong handle, jobject checkInDelegate) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); + + VerifyOrExit(wrapper != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + + ChipLogProgress(Controller, "setICDCheckInDelegate() called"); + + err = wrapper->SetICDCheckInDelegate(checkInDelegate); +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to set ICD Check-In Deleagate. : %" CHIP_ERROR_FORMAT, err.Format()); + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } +} + JNI_METHOD(void, commissionDevice) (JNIEnv * env, jobject self, jlong handle, jlong deviceId, jbyteArray csrNonce, jobject networkCredentials) { diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index d6b31c59dabcd5..9064d71755ceb2 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -151,6 +151,11 @@ public void finishOTAProvider() { finishOTAProvider(deviceControllerPtr); } + /** Set the delegate of ICD check in */ + public void setICDCheckInDelegate(ICDCheckInDelegate delegate) { + setICDCheckInDelegate(deviceControllerPtr, new ICDCheckInDelegateWrapper(delegate)); + } + public void pairDevice( BluetoothGatt bleServer, int connId, @@ -1434,6 +1439,9 @@ private native void setAttestationTrustStoreDelegate( private native void finishOTAProvider(long deviceControllerPtr); + private native void setICDCheckInDelegate( + long deviceControllerPtr, ICDCheckInDelegateWrapper delegate); + private native void pairDevice( long deviceControllerPtr, long deviceId, diff --git a/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegate.java b/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegate.java new file mode 100644 index 00000000000000..10b1eaaf9c64f6 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegate.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 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 javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Delegate for ICD check in. + * + *

See detailed in {@link ChipDeviceController#setICDCheckInDelegate(ICDCheckInDelegate)} + */ +public interface ICDCheckInDelegate { + /** + * Callback used to let the application know that a check-in message was received and validated. + * + * @param info ICDClientInfo object representing the state associated with the node that sent the + * check-in message. + */ + void onCheckInComplete(@Nonnull ICDClientInfo info); + + /** + * Callback used to let the application know that a key refresh is needed to avoid counter + * rollover problems. + * + * @param info ICDClientInfo object representing the state associated with the node that sent the + * check-in message. + * @return refreshed key + */ + @Nullable + byte[] onKeyRefreshNeeded(@Nonnull ICDClientInfo info); + + /** + * Callback used to let the application know that the re-registration process is done. + * + * @param errorCode to check for success and failure + */ + void onKeyRefreshDone(long errorCode); +} diff --git a/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegateWrapper.java b/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegateWrapper.java new file mode 100644 index 00000000000000..39ca5df1b9e766 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegateWrapper.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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; + +class ICDCheckInDelegateWrapper { + private ICDCheckInDelegate delegate; + + ICDCheckInDelegateWrapper(ICDCheckInDelegate delegate) { + this.delegate = delegate; + } + + // For JNI call + @SuppressWarnings("unused") + private void onCheckInComplete( + long peerNodeId, + long startCounter, + long offset, + long monitoredSubject, + byte[] icdAesKey, + byte[] icdHmacKey) { + delegate.onCheckInComplete( + new ICDClientInfo( + peerNodeId, startCounter, offset, monitoredSubject, icdAesKey, icdHmacKey)); + } + + @SuppressWarnings("unused") + private byte[] onKeyRefreshNeeded( + long peerNodeId, + long startCounter, + long offset, + long monitoredSubject, + byte[] icdAesKey, + byte[] icdHmacKey) { + return delegate.onKeyRefreshNeeded( + new ICDClientInfo( + peerNodeId, startCounter, offset, monitoredSubject, icdAesKey, icdHmacKey)); + } + + @SuppressWarnings("unused") + private void onKeyRefreshDone(int errorCode) { + delegate.onKeyRefreshDone(errorCode); + } +} From 0a6626f3aee3c813eeea88c481609d82bb3e2c3b Mon Sep 17 00:00:00 2001 From: Arkadiusz Bokowy Date: Fri, 15 Mar 2024 14:45:54 +0100 Subject: [PATCH 14/27] [Linux] Factor common code when connecting BLE device (#32502) --- src/platform/Linux/bluez/BluezEndpoint.cpp | 45 +++++++--------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/src/platform/Linux/bluez/BluezEndpoint.cpp b/src/platform/Linux/bluez/BluezEndpoint.cpp index fe7a5a25171f73..5203488a7aae82 100644 --- a/src/platform/Linux/bluez/BluezEndpoint.cpp +++ b/src/platform/Linux/bluez/BluezEndpoint.cpp @@ -307,7 +307,7 @@ CHIP_ERROR BluezEndpoint::RegisterGattApplicationImpl() /// Update the table of open BLE connections whenever a new device is spotted or its attributes have changed. void BluezEndpoint::UpdateConnectionTable(BluezDevice1 * apDevice) { - const char * objectPath = g_dbus_proxy_get_object_path(G_DBUS_PROXY(apDevice)); + const char * objectPath = g_dbus_proxy_get_object_path(reinterpret_cast(apDevice)); BluezConnection * connection = GetBluezConnection(objectPath); if (connection != nullptr && !bluez_device1_get_connected(apDevice)) @@ -321,22 +321,9 @@ void BluezEndpoint::UpdateConnectionTable(BluezDevice1 * apDevice) return; } - if (connection == nullptr && !bluez_device1_get_connected(apDevice) && mIsCentral) + if (connection == nullptr) { - return; - } - - if (connection == nullptr && bluez_device1_get_connected(apDevice) && - (!mIsCentral || bluez_device1_get_services_resolved(apDevice))) - { - connection = chip::Platform::New(*this, apDevice); - mpPeerDevicePath = g_strdup(objectPath); - mConnMap[mpPeerDevicePath] = connection; - - ChipLogDetail(DeviceLayer, "New BLE connection: conn %p, device %s, path %s", connection, connection->GetPeerAddress(), - mpPeerDevicePath); - - BLEManagerImpl::HandleNewConnection(connection); + HandleNewDevice(apDevice); } } @@ -355,28 +342,22 @@ void BluezEndpoint::BluezSignalInterfacePropertiesChanged(GDBusObjectManagerClie void BluezEndpoint::HandleNewDevice(BluezDevice1 * device) { - VerifyOrReturn(!mIsCentral); - - // We need to handle device connection both this function and BluezSignalInterfacePropertiesChanged - // When a device is connected for first time, this function will be triggered. - // The future connections for the same device will trigger ``Connect'' property change. - // TODO: Factor common code in the two function. - BluezConnection * conn; - VerifyOrExit(bluez_device1_get_connected(device), ChipLogError(DeviceLayer, "FAIL: device is not connected")); + VerifyOrReturn(bluez_device1_get_connected(device)); + VerifyOrReturn(!mIsCentral || bluez_device1_get_services_resolved(device)); - conn = GetBluezConnection(g_dbus_proxy_get_object_path(G_DBUS_PROXY(device))); - VerifyOrExit(conn == nullptr, - ChipLogError(DeviceLayer, "FAIL: connection already tracked: conn: %p new device: %s", conn, - g_dbus_proxy_get_object_path(G_DBUS_PROXY(device)))); + const char * objectPath = g_dbus_proxy_get_object_path(reinterpret_cast(device)); + BluezConnection * conn = GetBluezConnection(objectPath); + VerifyOrReturn(conn == nullptr, + ChipLogError(DeviceLayer, "FAIL: Connection already tracked: conn=%p device=%s path=%s", conn, + conn->GetPeerAddress(), objectPath)); conn = chip::Platform::New(*this, device); - mpPeerDevicePath = g_strdup(g_dbus_proxy_get_object_path(G_DBUS_PROXY(device))); + mpPeerDevicePath = g_strdup(objectPath); mConnMap[mpPeerDevicePath] = conn; - ChipLogDetail(DeviceLayer, "BLE device connected: conn %p, device %s, path %s", conn, conn->GetPeerAddress(), mpPeerDevicePath); + ChipLogDetail(DeviceLayer, "New BLE connection: conn=%p device=%s path=%s", conn, conn->GetPeerAddress(), objectPath); -exit: - return; + BLEManagerImpl::HandleNewConnection(conn); } void BluezEndpoint::BluezSignalOnObjectAdded(GDBusObjectManager * aManager, GDBusObject * aObject) From c56f3bf9e5bc7108060909d11cc078e81c2341ef Mon Sep 17 00:00:00 2001 From: Erwin Pan Date: Fri, 15 Mar 2024 22:22:05 +0800 Subject: [PATCH 15/27] [Chef] Implement DoorLock example app (#32532) * Update Chef Lock to enable user PIN and more * Remove unused clusters (Groups). Add Power Source * Refer to the latest lock-app codes * Update device PowerSource attributes * Enable PrivacyMode, Remove debug message * Fix typo * Restyled by whitespace * Restyled by clang-format * Add macro to doorlock to avoid compilation of light * [Chef] support doorlock in esp32 & nrfconnect * Update the date from 2023 to 2024 * Use CharSpan instead of "char *". Refine ifdef * Restyled by clang-format --------- Co-authored-by: Restyled.io --- .../door-lock/chef-doorlock-stubs.cpp | 133 +++ .../clusters/door-lock/chef-lock-endpoint.cpp | 680 +++++++++++++++ .../clusters/door-lock/chef-lock-endpoint.h | 162 ++++ .../clusters/door-lock/chef-lock-manager.cpp | 376 +++++++++ .../clusters/door-lock/chef-lock-manager.h | 78 ++ examples/chef/common/stubs.cpp | 232 ------ .../rootnode_doorlock_aNKYAreMXE.matter | 453 +++++++--- .../devices/rootnode_doorlock_aNKYAreMXE.zap | 786 ++++++++++++++---- examples/chef/esp32/main/CMakeLists.txt | 9 +- examples/chef/linux/BUILD.gn | 3 + examples/chef/nrfconnect/CMakeLists.txt | 11 +- 11 files changed, 2406 insertions(+), 517 deletions(-) create mode 100644 examples/chef/common/clusters/door-lock/chef-doorlock-stubs.cpp create mode 100644 examples/chef/common/clusters/door-lock/chef-lock-endpoint.cpp create mode 100644 examples/chef/common/clusters/door-lock/chef-lock-endpoint.h create mode 100644 examples/chef/common/clusters/door-lock/chef-lock-manager.cpp create mode 100644 examples/chef/common/clusters/door-lock/chef-lock-manager.h diff --git a/examples/chef/common/clusters/door-lock/chef-doorlock-stubs.cpp b/examples/chef/common/clusters/door-lock/chef-doorlock-stubs.cpp new file mode 100644 index 00000000000000..7d92300ca5659c --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-doorlock-stubs.cpp @@ -0,0 +1,133 @@ +/* + * + * Copyright (c) 2020-2024 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. + */ + +#include +#include +#include + +#ifdef MATTER_DM_PLUGIN_DOOR_LOCK_SERVER +#include "chef-lock-manager.h" +#include + +using namespace chip; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::DoorLock; +using chip::app::DataModel::Nullable; + +// ============================================================================= +// 'Default' callbacks for cluster commands +// ============================================================================= + +// App handles physical aspects of locking but not locking logic. That is it +// should wait for door to be locked on lock command and return success) but +// door lock server should check pin before even calling the lock-door +// callback. +bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) +{ + return LockManager::Instance().Lock(endpointId, fabricIdx, nodeId, pinCode, err, OperationSourceEnum::kRemote); +} + +bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) +{ + return LockManager::Instance().Unlock(endpointId, fabricIdx, nodeId, pinCode, err, OperationSourceEnum::kRemote); +} + +bool emberAfPluginDoorLockOnDoorUnboltCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pinCode, + OperationErrorEnum & err) +{ + return LockManager::Instance().Unbolt(endpointId, fabricIdx, nodeId, pinCode, err, OperationSourceEnum::kRemote); +} + +bool emberAfPluginDoorLockGetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) +{ + return LockManager::Instance().GetUser(endpointId, userIndex, user); +} + +bool emberAfPluginDoorLockSetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, const chip::CharSpan & userName, uint32_t uniqueId, + UserStatusEnum userStatus, UserTypeEnum usertype, CredentialRuleEnum credentialRule, + const CredentialStruct * credentials, size_t totalCredentials) +{ + + return LockManager::Instance().SetUser(endpointId, userIndex, creator, modifier, userName, uniqueId, userStatus, usertype, + credentialRule, credentials, totalCredentials); +} + +bool emberAfPluginDoorLockGetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) +{ + return LockManager::Instance().GetCredential(endpointId, credentialIndex, credentialType, credential); +} + +bool emberAfPluginDoorLockSetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, DlCredentialStatus credentialStatus, + CredentialTypeEnum credentialType, const chip::ByteSpan & credentialData) +{ + return LockManager::Instance().SetCredential(endpointId, credentialIndex, creator, modifier, credentialStatus, credentialType, + credentialData); +} + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule) +{ + return LockManager::Instance().GetSchedule(endpointId, weekdayIndex, userIndex, schedule); +} + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, + EmberAfPluginDoorLockHolidaySchedule & schedule) +{ + return LockManager::Instance().GetSchedule(endpointId, holidayIndex, schedule); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + DlScheduleStatus status, DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, + uint8_t endHour, uint8_t endMinute) +{ + return LockManager::Instance().SetSchedule(endpointId, weekdayIndex, userIndex, status, daysMask, startHour, startMinute, + endHour, endMinute); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime) +{ + return LockManager::Instance().SetSchedule(endpointId, yearDayIndex, userIndex, status, localStartTime, localEndTime); +} + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule) +{ + return LockManager::Instance().GetSchedule(endpointId, yearDayIndex, userIndex, schedule); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime, OperatingModeEnum operatingMode) +{ + return LockManager::Instance().SetSchedule(endpointId, holidayIndex, status, localStartTime, localEndTime, operatingMode); +} + +void emberAfDoorLockClusterInitCallback(EndpointId endpoint) +{ + DoorLockServer::Instance().InitServer(endpoint); + LockManager::Instance().InitEndpoint(endpoint); +} +#endif // MATTER_DM_PLUGIN_DOOR_LOCK_SERVER diff --git a/examples/chef/common/clusters/door-lock/chef-lock-endpoint.cpp b/examples/chef/common/clusters/door-lock/chef-lock-endpoint.cpp new file mode 100644 index 00000000000000..7cff212dba743b --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-lock-endpoint.cpp @@ -0,0 +1,680 @@ +/* + * + * Copyright (c) 2022-2024 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. + */ + +#include +#include +#include +#include +#include + +#ifdef MATTER_DM_PLUGIN_DOOR_LOCK_SERVER +#include "chef-lock-endpoint.h" + +using chip::to_underlying; +using chip::app::DataModel::MakeNullable; + +struct LockActionData +{ + chip::EndpointId endpointId; + DlLockState lockState; + OperationSourceEnum opSource; + Nullable userIndex; + uint16_t credentialIndex; + Nullable fabricIdx; + Nullable nodeId; + bool moving = false; +}; + +static LockActionData gCurrentAction; + +bool LockEndpoint::Lock(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource) +{ + return setLockState(fabricIdx, nodeId, DlLockState::kLocked, pin, err, opSource); +} + +bool LockEndpoint::Unlock(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource) +{ + if (DoorLockServer::Instance().SupportsUnbolt(mEndpointId)) + { + // If Unbolt is supported Unlock is supposed to pull the latch + return setLockState(fabricIdx, nodeId, DlLockState::kUnlatched, pin, err, opSource); + } + + return setLockState(fabricIdx, nodeId, DlLockState::kUnlocked, pin, err, opSource); +} + +bool LockEndpoint::Unbolt(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource) +{ + return setLockState(fabricIdx, nodeId, DlLockState::kUnlocked, pin, err, opSource); +} + +bool LockEndpoint::GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const +{ + ChipLogProgress(Zcl, "Lock App: LockEndpoint::GetUser [endpoint=%d,userIndex=%hu]", mEndpointId, userIndex); + + auto adjustedUserIndex = static_cast(userIndex - 1); + if (adjustedUserIndex > mLockUsers.size()) + { + ChipLogError(Zcl, "Cannot get user - index out of range [endpoint=%d,index=%hu,adjustedIndex=%d]", mEndpointId, userIndex, + adjustedUserIndex); + return false; + } + + const auto & userInDb = mLockUsers[adjustedUserIndex]; + user.userStatus = userInDb.userStatus; + if (UserStatusEnum::kAvailable == user.userStatus) + { + ChipLogDetail(Zcl, "Found unoccupied user [endpoint=%d,adjustedIndex=%hu]", mEndpointId, adjustedUserIndex); + return true; + } + + user.userName = userInDb.userName; + user.credentials = chip::Span(userInDb.credentials.data(), userInDb.credentials.size()); + user.userUniqueId = userInDb.userUniqueId; + user.userType = userInDb.userType; + user.credentialRule = userInDb.credentialRule; + // So far there's no way to actually create the credential outside the matter, so here we always set the creation/modification + // source to Matter + user.creationSource = DlAssetSource::kMatterIM; + user.createdBy = userInDb.createdBy; + user.modificationSource = DlAssetSource::kMatterIM; + user.lastModifiedBy = userInDb.lastModifiedBy; + + ChipLogDetail(Zcl, + "Found occupied user " + "[endpoint=%d,adjustedIndex=%hu,name=\"%.*s\",credentialsCount=%u,uniqueId=%x,type=%u,credentialRule=%u," + "createdBy=%d,lastModifiedBy=%d]", + mEndpointId, adjustedUserIndex, static_cast(user.userName.size()), user.userName.data(), + static_cast(user.credentials.size()), user.userUniqueId, to_underlying(user.userType), + to_underlying(user.credentialRule), user.createdBy, user.lastModifiedBy); + + return true; +} + +bool LockEndpoint::SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) +{ + ChipLogProgress(Zcl, + "Lock App: LockEndpoint::SetUser " + "[endpoint=%d,userIndex=%u,creator=%d,modifier=%d,userName=\"%.*s\",uniqueId=%" PRIx32 + ",userStatus=%u,userType=%u," + "credentialRule=%u,credentials=%p,totalCredentials=%u]", + mEndpointId, userIndex, creator, modifier, static_cast(userName.size()), userName.data(), uniqueId, + to_underlying(userStatus), to_underlying(usertype), to_underlying(credentialRule), credentials, + static_cast(totalCredentials)); + + auto adjustedUserIndex = static_cast(userIndex - 1); + if (adjustedUserIndex > mLockUsers.size()) + { + ChipLogError(Zcl, "Cannot set user - index out of range [endpoint=%d,index=%d,adjustedUserIndex=%u]", mEndpointId, + userIndex, adjustedUserIndex); + return false; + } + + auto & userInStorage = mLockUsers[adjustedUserIndex]; + if (userName.size() > DOOR_LOCK_MAX_USER_NAME_SIZE) + { + ChipLogError(Zcl, "Cannot set user - user name is too long [endpoint=%d,index=%d,adjustedUserIndex=%u]", mEndpointId, + userIndex, adjustedUserIndex); + return false; + } + + if (totalCredentials > userInStorage.credentials.capacity()) + { + ChipLogError(Zcl, + "Cannot set user - total number of credentials is too big [endpoint=%d,index=%d,adjustedUserIndex=%u" + ",totalCredentials=%u,maxNumberOfCredentials=%u]", + mEndpointId, userIndex, adjustedUserIndex, static_cast(totalCredentials), + static_cast(userInStorage.credentials.capacity())); + return false; + } + + userInStorage.userName = chip::MutableCharSpan(userInStorage.userNameBuf, DOOR_LOCK_USER_NAME_BUFFER_SIZE); + CopyCharSpanToMutableCharSpan(userName, userInStorage.userName); + userInStorage.userUniqueId = uniqueId; + userInStorage.userStatus = userStatus; + userInStorage.userType = usertype; + userInStorage.credentialRule = credentialRule; + userInStorage.lastModifiedBy = modifier; + userInStorage.createdBy = creator; + + userInStorage.credentials.clear(); + for (size_t i = 0; i < totalCredentials; ++i) + { + userInStorage.credentials.push_back(credentials[i]); + } + + ChipLogProgress(Zcl, "Successfully set the user [mEndpointId=%d,index=%d,adjustedIndex=%d]", mEndpointId, userIndex, + adjustedUserIndex); + + return true; +} + +DoorStateEnum LockEndpoint::GetDoorState() const +{ + return mDoorState; +} + +bool LockEndpoint::SetDoorState(DoorStateEnum newState) +{ + if (mDoorState != newState) + { + ChipLogProgress(Zcl, "Changing the door state to: %d [endpointId=%d,previousState=%d]", to_underlying(newState), + mEndpointId, to_underlying(mDoorState)); + + mDoorState = newState; + return DoorLockServer::Instance().SetDoorState(mEndpointId, mDoorState); + } + return true; +} + +bool LockEndpoint::SendLockAlarm(AlarmCodeEnum alarmCode) const +{ + ChipLogProgress(Zcl, "Sending the LockAlarm event [endpointId=%d,alarmCode=%u]", mEndpointId, to_underlying(alarmCode)); + return DoorLockServer::Instance().SendLockAlarmEvent(mEndpointId, alarmCode); +} + +bool LockEndpoint::GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) const +{ + ChipLogProgress(Zcl, "Lock App: LockEndpoint::GetCredential [endpoint=%d,credentialIndex=%u,credentialType=%u]", mEndpointId, + credentialIndex, to_underlying(credentialType)); + + if (to_underlying(credentialType) >= mLockCredentials.size()) + { + ChipLogError(Zcl, "Cannot get the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); + return false; + } + + if (credentialIndex >= mLockCredentials.at(to_underlying(credentialType)).size() || + (0 == credentialIndex && CredentialTypeEnum::kProgrammingPIN != credentialType)) + { + ChipLogError(Zcl, "Cannot get the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); + return false; + } + + const auto & credentialInStorage = mLockCredentials[to_underlying(credentialType)][credentialIndex]; + + credential.status = credentialInStorage.status; + if (DlCredentialStatus::kAvailable == credential.status) + { + ChipLogDetail(Zcl, "Found unoccupied credential [endpoint=%d,index=%u]", mEndpointId, credentialIndex); + return true; + } + credential.credentialType = credentialInStorage.credentialType; + credential.credentialData = chip::ByteSpan(credentialInStorage.credentialData, credentialInStorage.credentialDataSize); + // So far there's no way to actually create the credential outside the matter, so here we always set the creation/modification + // source to Matter + credential.creationSource = DlAssetSource::kMatterIM; + credential.createdBy = credentialInStorage.createdBy; + credential.modificationSource = DlAssetSource::kMatterIM; + credential.lastModifiedBy = credentialInStorage.modifiedBy; + + ChipLogDetail(Zcl, "Found occupied credential [endpoint=%d,index=%u,type=%u,dataSize=%u,createdBy=%u,modifiedBy=%u]", + mEndpointId, credentialIndex, to_underlying(credential.credentialType), + static_cast(credential.credentialData.size()), credential.createdBy, credential.lastModifiedBy); + + return true; +} + +bool LockEndpoint::SetCredential(uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData) +{ + ChipLogProgress( + Zcl, + "Lock App: LockEndpoint::SetCredential " + "[endpoint=%d,credentialIndex=%u,credentialStatus=%u,credentialType=%u,credentialDataSize=%u,creator=%u,modifier=%u]", + mEndpointId, credentialIndex, to_underlying(credentialStatus), to_underlying(credentialType), + static_cast(credentialData.size()), creator, modifier); + + if (to_underlying(credentialType) >= mLockCredentials.capacity()) + { + ChipLogError(Zcl, "Cannot set the credential - type out of range [endpoint=%d,type=%d]", mEndpointId, + to_underlying(credentialType)); + return false; + } + + if (credentialIndex >= mLockCredentials.at(to_underlying(credentialType)).size() || + (0 == credentialIndex && CredentialTypeEnum::kProgrammingPIN != credentialType)) + { + ChipLogError(Zcl, "Cannot set the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); + return false; + } + + // Assign to array by credentialIndex. Note: 0 is reserved for programmingPIN only + auto & credentialInStorage = mLockCredentials[to_underlying(credentialType)][credentialIndex]; + if (credentialData.size() > DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE) + { + ChipLogError(Zcl, + "Cannot get the credential - data size exceeds limit " + "[endpoint=%d,index=%d,dataSize=%u,maxDataSize=%u]", + mEndpointId, credentialIndex, static_cast(credentialData.size()), + static_cast(DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE)); + return false; + } + credentialInStorage.status = credentialStatus; + credentialInStorage.credentialType = credentialType; + credentialInStorage.createdBy = creator; + credentialInStorage.modifiedBy = modifier; + std::memcpy(credentialInStorage.credentialData, credentialData.data(), credentialData.size()); + credentialInStorage.credentialDataSize = credentialData.size(); + + ChipLogProgress(Zcl, "Successfully set the credential [mEndpointId=%d,index=%d,credentialType=%u,creator=%u,modifier=%u]", + mEndpointId, credentialIndex, to_underlying(credentialType), credentialInStorage.createdBy, + credentialInStorage.modifiedBy); + + return true; +} + +DlStatus LockEndpoint::GetSchedule(uint8_t weekDayIndex, uint16_t userIndex, EmberAfPluginDoorLockWeekDaySchedule & schedule) +{ + if (0 == userIndex || userIndex > mWeekDaySchedules.size()) + { + return DlStatus::kFailure; + } + + if (0 == weekDayIndex || weekDayIndex > mWeekDaySchedules.at(userIndex - 1).size()) + { + return DlStatus::kFailure; + } + + const auto & scheduleInStorage = mWeekDaySchedules.at(userIndex - 1).at(weekDayIndex - 1); + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::SetSchedule(uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, DaysMaskMap daysMask, + uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) +{ + if (0 == userIndex || userIndex > mWeekDaySchedules.size()) + { + return DlStatus::kFailure; + } + + if (0 == weekDayIndex || weekDayIndex > mWeekDaySchedules.at(userIndex - 1).size()) + { + return DlStatus::kFailure; + } + + auto & scheduleInStorage = mWeekDaySchedules.at(userIndex - 1).at(weekDayIndex - 1); + + scheduleInStorage.schedule.daysMask = daysMask; + scheduleInStorage.schedule.startHour = startHour; + scheduleInStorage.schedule.startMinute = startMinute; + scheduleInStorage.schedule.endHour = endHour; + scheduleInStorage.schedule.endMinute = endMinute; + scheduleInStorage.status = status; + + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::GetSchedule(uint8_t yearDayIndex, uint16_t userIndex, EmberAfPluginDoorLockYearDaySchedule & schedule) +{ + if (0 == userIndex || userIndex > mYearDaySchedules.size()) + { + return DlStatus::kFailure; + } + + if (0 == yearDayIndex || yearDayIndex > mYearDaySchedules.at(userIndex - 1).size()) + { + return DlStatus::kFailure; + } + + const auto & scheduleInStorage = mYearDaySchedules.at(userIndex - 1).at(yearDayIndex - 1); + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::SetSchedule(uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime) +{ + if (0 == userIndex || userIndex > mYearDaySchedules.size()) + { + return DlStatus::kFailure; + } + + if (0 == yearDayIndex || yearDayIndex > mYearDaySchedules.at(userIndex - 1).size()) + { + return DlStatus::kFailure; + } + + auto & scheduleInStorage = mYearDaySchedules.at(userIndex - 1).at(yearDayIndex - 1); + scheduleInStorage.schedule.localStartTime = localStartTime; + scheduleInStorage.schedule.localEndTime = localEndTime; + scheduleInStorage.status = status; + + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::GetSchedule(uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule & schedule) +{ + if (0 == holidayIndex || holidayIndex > mHolidaySchedules.size()) + { + return DlStatus::kFailure; + } + + const auto & scheduleInStorage = mHolidaySchedules[holidayIndex - 1]; + if (DlScheduleStatus::kAvailable == scheduleInStorage.status) + { + return DlStatus::kNotFound; + } + + schedule = scheduleInStorage.schedule; + return DlStatus::kSuccess; +} + +DlStatus LockEndpoint::SetSchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime, + OperatingModeEnum operatingMode) +{ + if (0 == holidayIndex || holidayIndex > mHolidaySchedules.size()) + { + return DlStatus::kFailure; + } + + auto & scheduleInStorage = mHolidaySchedules[holidayIndex - 1]; + scheduleInStorage.schedule.localStartTime = localStartTime; + scheduleInStorage.schedule.localEndTime = localEndTime; + scheduleInStorage.schedule.operatingMode = operatingMode; + scheduleInStorage.status = status; + + return DlStatus::kSuccess; +} + +bool LockEndpoint::setLockState(const Nullable & fabricIdx, const Nullable & nodeId, + DlLockState lockState, const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource) +{ + // Assume pin is required until told otherwise + bool requirePin = true; + chip::app::Clusters::DoorLock::Attributes::RequirePINforRemoteOperation::Get(mEndpointId, &requirePin); + + // If a pin code is not given + if (!pin.HasValue()) + { + ChipLogDetail(Zcl, "Door Lock App: PIN code is not specified [endpointId=%d]", mEndpointId); + + // If a pin code is not required + if (!requirePin) + { + ChipLogProgress(Zcl, "Door Lock App: setting door lock state to \"%s\" [endpointId=%d]", lockStateToString(lockState), + mEndpointId); + + if (gCurrentAction.moving == true) + { + ChipLogProgress(Zcl, "Lock App: not executing lock action as another lock action is already active [endpointId=%d]", + mEndpointId); + return false; + } + + gCurrentAction.moving = true; + gCurrentAction.endpointId = mEndpointId; + gCurrentAction.lockState = lockState; + gCurrentAction.opSource = opSource; + gCurrentAction.userIndex = NullNullable; + gCurrentAction.fabricIdx = fabricIdx; + gCurrentAction.nodeId = nodeId; + + // Do this async as a real lock would do too but use 0s delay to speed up CI tests + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); + + return true; + } + + ChipLogError(Zcl, "Door Lock App: PIN code is not specified, but it is required [endpointId=%d]", mEndpointId); + + err = OperationErrorEnum::kInvalidCredential; + return false; + } + + // Find the credential so we can make sure it is not absent right away + auto & pinCredentials = mLockCredentials[to_underlying(CredentialTypeEnum::kPin)]; + auto credential = std::find_if(pinCredentials.begin(), pinCredentials.end(), [&pin](const LockCredentialInfo & c) { + return (c.status != DlCredentialStatus::kAvailable) && + chip::ByteSpan{ c.credentialData, c.credentialDataSize }.data_equal(pin.Value()); + }); + if (credential == pinCredentials.end()) + { + ChipLogDetail(Zcl, + "Lock App: specified PIN code was not found in the database, ignoring command to set lock state to \"%s\" " + "[endpointId=%d]", + lockStateToString(lockState), mEndpointId); + + err = OperationErrorEnum::kInvalidCredential; + return false; + } + + // Find a user that correspond to this credential + auto credentialIndex = static_cast(credential - pinCredentials.begin()); + auto user = std::find_if(mLockUsers.begin(), mLockUsers.end(), [credential, credentialIndex](const LockUserInfo & u) { + return std::any_of(u.credentials.begin(), u.credentials.end(), [&credential, credentialIndex](const CredentialStruct & c) { + return c.credentialIndex == credentialIndex && c.credentialType == credential->credentialType; + }); + }); + if (user == mLockUsers.end()) + { + ChipLogDetail(Zcl, + "Lock App: specified PIN code was found in the database, but the lock user is not associated with it " + "[endpointId=%d,credentialIndex=%u]", + mEndpointId, credentialIndex); + } + + auto userIndex = static_cast(user - mLockUsers.begin()); + + // Check if schedules affect the user + bool haveWeekDaySchedules = false; + bool haveYearDaySchedules = false; + if (weekDayScheduleForbidsAccess(userIndex, &haveWeekDaySchedules) || + yearDayScheduleForbidsAccess(userIndex, &haveYearDaySchedules) || + // Also disallow access for a user that's supposed to have _some_ + // schedule but doesn't have any + (user->userType == UserTypeEnum::kScheduleRestrictedUser && !haveWeekDaySchedules && !haveYearDaySchedules)) + { + ChipLogDetail(Zcl, + "Lock App: associated user is not allowed to operate the lock due to schedules" + "[endpointId=%d,userIndex=%u]", + mEndpointId, userIndex); + err = OperationErrorEnum::kRestricted; + return false; + } + ChipLogProgress( + Zcl, + "Lock App: specified PIN code was found in the database, setting door lock state to \"%s\" [endpointId=%d,userIndex=%u]", + lockStateToString(lockState), mEndpointId, userIndex); + + if (gCurrentAction.moving == true) + { + ChipLogProgress(Zcl, + "Lock App: not executing lock action as another lock action is already active [endpointId=%d,userIndex=%u]", + mEndpointId, userIndex); + return false; + } + + gCurrentAction.moving = true; + gCurrentAction.endpointId = mEndpointId; + gCurrentAction.lockState = lockState; + gCurrentAction.opSource = opSource; + gCurrentAction.userIndex = MakeNullable(static_cast(userIndex + 1)); + gCurrentAction.credentialIndex = static_cast(credentialIndex); + gCurrentAction.fabricIdx = fabricIdx; + gCurrentAction.nodeId = nodeId; + + // Do this async as a real lock would do too but use 0s delay to speed up CI tests + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); + + return true; +} + +void LockEndpoint::OnLockActionCompleteCallback(chip::System::Layer *, void * callbackContext) +{ + if (gCurrentAction.userIndex.IsNull()) + { + DoorLockServer::Instance().SetLockState(gCurrentAction.endpointId, gCurrentAction.lockState, gCurrentAction.opSource, + NullNullable, NullNullable, gCurrentAction.fabricIdx, gCurrentAction.nodeId); + } + else + { + LockOpCredentials userCredential[] = { { CredentialTypeEnum::kPin, gCurrentAction.credentialIndex } }; + auto userCredentials = MakeNullable>(userCredential); + + DoorLockServer::Instance().SetLockState(gCurrentAction.endpointId, gCurrentAction.lockState, gCurrentAction.opSource, + gCurrentAction.userIndex, userCredentials, gCurrentAction.fabricIdx, + gCurrentAction.nodeId); + } + + // move back to Unlocked after Unlatch + if (gCurrentAction.lockState == DlLockState::kUnlatched) + { + gCurrentAction.lockState = DlLockState::kUnlocked; + + // Do this async as a real lock would do too but use 0s delay to speed up CI tests + chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); + } + else + { + gCurrentAction.moving = false; + } +} + +bool LockEndpoint::weekDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const +{ + *haveSchedule = std::any_of(mWeekDaySchedules[userIndex].begin(), mWeekDaySchedules[userIndex].end(), + [](const WeekDaysScheduleInfo & s) { return s.status == DlScheduleStatus::kOccupied; }); + + const auto & user = mLockUsers[userIndex]; + if (user.userType != UserTypeEnum::kScheduleRestrictedUser && user.userType != UserTypeEnum::kWeekDayScheduleUser) + { + // Weekday schedules don't apply to this user. + return false; + } + + if (user.userType == UserTypeEnum::kScheduleRestrictedUser && !*haveSchedule) + { + // It's valid to not have any schedules of a given type; on its own this + // does not prevent access. + return false; + } + + chip::System::Clock::Milliseconds64 cTMs; + auto chipError = chip::System::SystemClock().GetClock_RealTimeMS(cTMs); + if (chipError != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "Lock App: unable to get current time to check user schedules [endpointId=%d,error=%d (%s)]", mEndpointId, + chipError.AsInteger(), chipError.AsString()); + return true; + } + time_t unixEpoch = std::chrono::duration_cast(cTMs).count(); + + tm calendarTime{}; + localtime_r(&unixEpoch, &calendarTime); + + auto currentTime = + calendarTime.tm_hour * chip::kSecondsPerHour + calendarTime.tm_min * chip::kSecondsPerMinute + calendarTime.tm_sec; + + // Now check whether any schedule allows the current time. If it does, + // access is not forbidden. + return !std::any_of( + mWeekDaySchedules[userIndex].begin(), mWeekDaySchedules[userIndex].end(), + [currentTime, calendarTime](const WeekDaysScheduleInfo & s) { + auto startTime = s.schedule.startHour * chip::kSecondsPerHour + s.schedule.startMinute * chip::kSecondsPerMinute; + auto endTime = s.schedule.endHour * chip::kSecondsPerHour + s.schedule.endMinute * chip::kSecondsPerMinute; + + return s.status == DlScheduleStatus::kOccupied && (to_underlying(s.schedule.daysMask) & (1 << calendarTime.tm_wday)) && + startTime <= currentTime && currentTime <= endTime; + }); +} + +bool LockEndpoint::yearDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const +{ + *haveSchedule = std::any_of(mYearDaySchedules[userIndex].begin(), mYearDaySchedules[userIndex].end(), + [](const YearDayScheduleInfo & sch) { return sch.status == DlScheduleStatus::kOccupied; }); + + const auto & user = mLockUsers[userIndex]; + if (user.userType != UserTypeEnum::kScheduleRestrictedUser && user.userType != UserTypeEnum::kYearDayScheduleUser) + { + return false; + } + + if (user.userType == UserTypeEnum::kScheduleRestrictedUser && !*haveSchedule) + { + // It's valid to not have any schedules of a given type; on its own this + // does not prevent access. + return false; + } + + chip::System::Clock::Milliseconds64 cTMs; + auto chipError = chip::System::SystemClock().GetClock_RealTimeMS(cTMs); + if (chipError != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "Lock App: unable to get current time to check user schedules [endpointId=%d,error=%d (%s)]", mEndpointId, + chipError.AsInteger(), chipError.AsString()); + return true; + } + auto unixEpoch = std::chrono::duration_cast(cTMs).count(); + uint32_t chipEpoch = 0; + if (!chip::UnixEpochToChipEpochTime(unixEpoch, chipEpoch)) + { + ChipLogError(Zcl, + "Lock App: unable to convert Unix Epoch time to Matter Epoch Time to check user schedules " + "[endpointId=%d,userIndex=%d]", + mEndpointId, userIndex); + return false; + } + + return !std::any_of(mYearDaySchedules[userIndex].begin(), mYearDaySchedules[userIndex].end(), + [chipEpoch](const YearDayScheduleInfo & sch) { + return sch.status == DlScheduleStatus::kOccupied && sch.schedule.localStartTime <= chipEpoch && + chipEpoch <= sch.schedule.localEndTime; + }); +} + +const char * LockEndpoint::lockStateToString(DlLockState lockState) const +{ + switch (lockState) + { + case DlLockState::kNotFullyLocked: + return "Not Fully Locked"; + case DlLockState::kLocked: + return "Locked"; + case DlLockState::kUnlocked: + return "Unlocked"; + case DlLockState::kUnlatched: + return "Unlatched"; + case DlLockState::kUnknownEnumValue: + break; + } + + return "Unknown"; +} +#endif // MATTER_DM_PLUGIN_DOOR_LOCK_SERVER diff --git a/examples/chef/common/clusters/door-lock/chef-lock-endpoint.h b/examples/chef/common/clusters/door-lock/chef-lock-endpoint.h new file mode 100644 index 00000000000000..285e703dfaf73c --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-lock-endpoint.h @@ -0,0 +1,162 @@ +/* + * + * Copyright (c) 2022-2024 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. + */ + +#pragma once + +#include +#include + +struct LockUserInfo +{ + char userNameBuf[DOOR_LOCK_USER_NAME_BUFFER_SIZE]; + chip::MutableCharSpan userName; + uint32_t userUniqueId; + UserStatusEnum userStatus; + UserTypeEnum userType; + CredentialRuleEnum credentialRule; + std::vector credentials; + chip::FabricIndex createdBy; + chip::FabricIndex lastModifiedBy; +}; + +struct LockCredentialInfo; +struct WeekDaysScheduleInfo; +struct YearDayScheduleInfo; +struct HolidayScheduleInfo; + +static constexpr size_t DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE = 20; +static constexpr size_t DOOR_LOCK_CREDENTIAL_INFO_MAX_TYPES = 6; // 0: ProgrammingPIN ~ 5: Face + +class LockEndpoint +{ +public: + LockEndpoint(chip::EndpointId endpointId, uint16_t numberOfLockUsersSupported, uint16_t numberOfCredentialsSupported, + uint8_t weekDaySchedulesPerUser, uint8_t yearDaySchedulesPerUser, uint8_t numberOfCredentialsPerUser, + uint8_t numberOfHolidaySchedules) : + mEndpointId{ endpointId }, + mLockState{ DlLockState::kLocked }, mDoorState{ DoorStateEnum::kDoorClosed }, mLockUsers(numberOfLockUsersSupported), + mLockCredentials(DOOR_LOCK_CREDENTIAL_INFO_MAX_TYPES, std::vector(numberOfCredentialsSupported + 1)), + mWeekDaySchedules(numberOfLockUsersSupported, std::vector(weekDaySchedulesPerUser)), + mYearDaySchedules(numberOfLockUsersSupported, std::vector(yearDaySchedulesPerUser)), + mHolidaySchedules(numberOfHolidaySchedules) + { + for (auto & lockUser : mLockUsers) + { + lockUser.credentials.reserve(numberOfCredentialsPerUser); + } + DoorLockServer::Instance().SetDoorState(endpointId, mDoorState); + DoorLockServer::Instance().SetLockState(endpointId, mLockState); + } + + inline chip::EndpointId GetEndpointId() const { return mEndpointId; } + + bool Lock(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + bool Unlock(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + bool Unbolt(const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + + bool GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const; + bool SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, const chip::CharSpan & userName, + uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, CredentialRuleEnum credentialRule, + const CredentialStruct * credentials, size_t totalCredentials); + + bool SetDoorState(DoorStateEnum newState); + + DoorStateEnum GetDoorState() const; + + bool SendLockAlarm(AlarmCodeEnum alarmCode) const; + + bool GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) const; + + bool SetCredential(uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData); + + DlStatus GetSchedule(uint8_t weekDayIndex, uint16_t userIndex, EmberAfPluginDoorLockWeekDaySchedule & schedule); + DlStatus GetSchedule(uint8_t yearDayIndex, uint16_t userIndex, EmberAfPluginDoorLockYearDaySchedule & schedule); + DlStatus GetSchedule(uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule & schedule); + + DlStatus SetSchedule(uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, DaysMaskMap daysMask, uint8_t startHour, + uint8_t startMinute, uint8_t endHour, uint8_t endMinute); + DlStatus SetSchedule(uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime); + DlStatus SetSchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime, + OperatingModeEnum operatingMode); + +private: + bool setLockState(const Nullable & fabricIdx, const Nullable & nodeId, DlLockState lockState, + const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource = OperationSourceEnum::kUnspecified); + const char * lockStateToString(DlLockState lockState) const; + + // Returns true if week day schedules should apply to the user, there are + // schedules defined for the user, and access is not currently allowed by + // those schedules. The outparam indicates whether there were in fact any + // year day schedules defined for the user. + bool weekDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const; + // Returns true if year day schedules should apply to the user, there are + // schedules defined for the user, and access is not currently allowed by + // those schedules. The outparam indicates whether there were in fact any + // year day schedules defined for the user. + bool yearDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const; + + static void OnLockActionCompleteCallback(chip::System::Layer *, void * callbackContext); + + chip::EndpointId mEndpointId; + DlLockState mLockState; + DoorStateEnum mDoorState; + + // This is very naive implementation of users/credentials/schedules database and by no means the best practice. Proper storage + // of those items is out of scope of this example. + std::vector mLockUsers; + std::vector> mLockCredentials; + std::vector> mWeekDaySchedules; + std::vector> mYearDaySchedules; + std::vector mHolidaySchedules; +}; + +struct LockCredentialInfo +{ + DlCredentialStatus status; + CredentialTypeEnum credentialType; + chip::FabricIndex createdBy; + chip::FabricIndex modifiedBy; + uint8_t credentialData[DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE]; + size_t credentialDataSize; +}; + +struct WeekDaysScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockWeekDaySchedule schedule; +}; + +struct YearDayScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockYearDaySchedule schedule; +}; + +struct HolidayScheduleInfo +{ + DlScheduleStatus status; + EmberAfPluginDoorLockHolidaySchedule schedule; +}; diff --git a/examples/chef/common/clusters/door-lock/chef-lock-manager.cpp b/examples/chef/common/clusters/door-lock/chef-lock-manager.cpp new file mode 100644 index 00000000000000..0b81cce895b264 --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-lock-manager.cpp @@ -0,0 +1,376 @@ +/* + * + * Copyright (c) 2020-2024 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. + */ +#include +#include +#include + +#ifdef MATTER_DM_PLUGIN_DOOR_LOCK_SERVER +#include "chef-lock-manager.h" + +using chip::to_underlying; + +LockManager LockManager::instance; + +LockManager & LockManager::Instance() +{ + return instance; +} + +bool LockManager::InitEndpoint(chip::EndpointId endpointId) +{ + uint16_t numberOfSupportedUsers = 0; + if (!DoorLockServer::Instance().GetNumberOfUserSupported(endpointId, numberOfSupportedUsers)) + { + ChipLogError(Zcl, + "Unable to get number of supported users when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfSupportedUsers = 10; + } + + uint16_t numberOfSupportedCredentials = 0; + // We're planning to use shared storage for PIN and RFID users so we will have the maximum of both sizes her to simplify logic + uint16_t numberOfPINCredentialsSupported = 0; + uint16_t numberOfRFIDCredentialsSupported = 0; + if (!DoorLockServer::Instance().GetNumberOfPINCredentialsSupported(endpointId, numberOfPINCredentialsSupported) || + !DoorLockServer::Instance().GetNumberOfRFIDCredentialsSupported(endpointId, numberOfRFIDCredentialsSupported)) + { + ChipLogError( + Zcl, "Unable to get number of supported credentials when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfSupportedCredentials = 10; + } + else + { + numberOfSupportedCredentials = std::max(numberOfPINCredentialsSupported, numberOfRFIDCredentialsSupported); + } + + uint8_t numberOfCredentialsSupportedPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfCredentialsSupportedPerUser(endpointId, numberOfCredentialsSupportedPerUser)) + { + ChipLogError(Zcl, + "Unable to get number of credentials supported per user when initializing lock endpoint, defaulting to 5 " + "[endpointId=%d]", + endpointId); + numberOfCredentialsSupportedPerUser = 5; + } + + uint8_t numberOfWeekDaySchedulesPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfWeekDaySchedulesPerUserSupported(endpointId, numberOfWeekDaySchedulesPerUser)) + { + ChipLogError(Zcl, + "Unable to get number of supported week day schedules per user when initializing lock endpoint, defaulting to " + "10 [endpointId=%d]", + endpointId); + numberOfWeekDaySchedulesPerUser = 10; + } + + uint8_t numberOfYearDaySchedulesPerUser = 0; + if (!DoorLockServer::Instance().GetNumberOfYearDaySchedulesPerUserSupported(endpointId, numberOfYearDaySchedulesPerUser)) + { + ChipLogError(Zcl, + "Unable to get number of supported year day schedules per user when initializing lock endpoint, defaulting to " + "10 [endpointId=%d]", + endpointId); + numberOfYearDaySchedulesPerUser = 10; + } + + uint8_t numberOfHolidaySchedules = 0; + if (!DoorLockServer::Instance().GetNumberOfHolidaySchedulesSupported(endpointId, numberOfHolidaySchedules)) + { + ChipLogError( + Zcl, + "Unable to get number of supported holiday schedules when initializing lock endpoint, defaulting to 10 [endpointId=%d]", + endpointId); + numberOfHolidaySchedules = 10; + } + + mEndpoints.emplace_back(endpointId, numberOfSupportedUsers, numberOfSupportedCredentials, numberOfWeekDaySchedulesPerUser, + numberOfYearDaySchedulesPerUser, numberOfCredentialsSupportedPerUser, numberOfHolidaySchedules); + + // Refer to 5.2.10.34. SetUser Command, when Creat a new user record + // - UserIndex value SHALL be set to a user record with UserType set to Available + // - UserName MAY be null causing new user record to use empty string for UserName + // otherwise UserName SHALL be set to the value provided in the new user record. + // - UserUniqueID MAY be null causing new user record to use 0xFFFFFFFF for UserUniqueID + // otherwise UserUniqueID SHALL be set to the value provided in the new user record + // - UserStatus MAY be null causing new user record to use OccupiedEnabled for UserStatus + // otherwise UserStatus SHALL be set to the value provided in the new user record + // - UserType MAY be null causing new user record to use UnrestrictedUser for UserType + // otherwise UserType SHALL be set to the value provided in the new user record. + uint16_t userIndex(1); + chip::FabricIndex creator(1); + chip::FabricIndex modifier(1); + const chip::CharSpan userName = chip::CharSpan::fromCharString("user1"); // default + // username + uint32_t uniqueId = 0xFFFFFFFF; // null + UserStatusEnum userStatus = UserStatusEnum::kOccupiedEnabled; + // Set to programming user instead of unrestrict user to perform + // priviledged function + UserTypeEnum usertype = UserTypeEnum::kProgrammingUser; + CredentialRuleEnum credentialRule = CredentialRuleEnum::kSingle; + + constexpr size_t totalCredentials(2); + // According to spec (5.2.6.26.2. CredentialIndex Field), programming PIN credential should be always indexed as 0 + uint16_t credentialIndex0(0); + // 1st non ProgrammingPIN credential should be indexed as 1 + uint16_t credentialIndex1(1); + + const CredentialStruct credentials[totalCredentials] = { + { credentialType : CredentialTypeEnum::kProgrammingPIN, credentialIndex : credentialIndex0 }, + { credentialType : CredentialTypeEnum::kPin, credentialIndex : credentialIndex1 } + }; + + if (!SetUser(endpointId, userIndex, creator, modifier, userName, uniqueId, userStatus, usertype, credentialRule, + &credentials[0], totalCredentials)) + { + ChipLogError(Zcl, "Unable to set the User [endpointId=%d]", endpointId); + return false; + } + + DlCredentialStatus credentialStatus = DlCredentialStatus::kOccupied; + + // Set the default user's ProgrammingPIN credential + uint8_t defaultProgrammingPIN[6] = { 0x39, 0x39, 0x39, 0x39, 0x39, 0x39 }; // 000000 + if (!SetCredential(endpointId, credentialIndex0, creator, modifier, credentialStatus, CredentialTypeEnum::kProgrammingPIN, + chip::ByteSpan(defaultProgrammingPIN))) + { + ChipLogError(Zcl, "Unable to set the credential - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + + // Set the default user's non ProgrammingPIN credential + uint8_t defaultPin[6] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; // 123456 + if (!SetCredential(endpointId, credentialIndex1, creator, modifier, credentialStatus, CredentialTypeEnum::kPin, + chip::ByteSpan(defaultPin))) + { + ChipLogError(Zcl, "Unable to set the credential - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + + ChipLogProgress(Zcl, + "Initialized new lock door endpoint " + "[id=%d,users=%d,credentials=%d,weekDaySchedulesPerUser=%d,yearDaySchedulesPerUser=%d," + "numberOfCredentialsSupportedPerUser=%d,holidaySchedules=%d]", + endpointId, numberOfSupportedUsers, numberOfSupportedCredentials, numberOfWeekDaySchedulesPerUser, + numberOfYearDaySchedulesPerUser, numberOfCredentialsSupportedPerUser, numberOfHolidaySchedules); + + return true; +} + +bool LockManager::SetDoorState(chip::EndpointId endpointId, DoorStateEnum doorState) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to toggle the door state - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return false; + } + return lockEndpoint->SetDoorState(doorState); +} + +bool LockManager::SendLockAlarm(chip::EndpointId endpointId, AlarmCodeEnum alarmCode) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to send lock alarm - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->SendLockAlarm(alarmCode); +} + +bool LockManager::Lock(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to lock the door - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->Lock(fabricIdx, nodeId, pin, err, opSource); +} + +bool LockManager::Unlock(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to unlock the door - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->Unlock(fabricIdx, nodeId, pin, err, opSource); +} + +bool LockManager::Unbolt(chip::EndpointId endpointId, const Nullable & fabricIdx, + const Nullable & nodeId, const Optional & pin, OperationErrorEnum & err, + OperationSourceEnum opSource) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to unbolt the door - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->Unbolt(fabricIdx, nodeId, pin, err, opSource); +} + +bool LockManager::GetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the user - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->GetUser(userIndex, user); +} + +bool LockManager::SetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the user - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->SetUser(userIndex, creator, modifier, userName, uniqueId, userStatus, usertype, credentialRule, + credentials, totalCredentials); +} + +bool LockManager::GetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the credential - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->GetCredential(credentialIndex, credentialType, credential); +} + +bool LockManager::SetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, + chip::FabricIndex modifier, DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the credential - endpoint does not exist or not initialized [endpointId=%d]", endpointId); + return false; + } + return lockEndpoint->SetCredential(credentialIndex, creator, modifier, credentialStatus, credentialType, credentialData); +} + +DlStatus LockManager::GetSchedule(chip::EndpointId endpointId, uint8_t weekDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the week day schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->GetSchedule(weekDayIndex, userIndex, schedule); +} + +DlStatus LockManager::SetSchedule(chip::EndpointId endpointId, uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, + DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the week day schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->SetSchedule(weekDayIndex, userIndex, status, daysMask, startHour, startMinute, endHour, endMinute); +} + +DlStatus LockManager::GetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the year day schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->GetSchedule(yearDayIndex, userIndex, schedule); +} + +DlStatus LockManager::SetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the year day schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->SetSchedule(yearDayIndex, userIndex, status, localStartTime, localEndTime); +} + +DlStatus LockManager::GetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, + EmberAfPluginDoorLockHolidaySchedule & schedule) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to get the holiday schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->GetSchedule(holidayIndex, schedule); +} + +DlStatus LockManager::SetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime, OperatingModeEnum operatingMode) +{ + auto lockEndpoint = getEndpoint(endpointId); + if (nullptr == lockEndpoint) + { + ChipLogError(Zcl, "Unable to set the holiday schedule - endpoint does not exist or not initialized [endpointId=%d]", + endpointId); + return DlStatus::kFailure; + } + return lockEndpoint->SetSchedule(holidayIndex, status, localStartTime, localEndTime, operatingMode); +} + +LockEndpoint * LockManager::getEndpoint(chip::EndpointId endpointId) +{ + for (auto & mEndpoint : mEndpoints) + { + if (mEndpoint.GetEndpointId() == endpointId) + { + return &mEndpoint; + } + } + return nullptr; +} +#endif // MATTER_DM_PLUGIN_DOOR_LOCK_SERVER diff --git a/examples/chef/common/clusters/door-lock/chef-lock-manager.h b/examples/chef/common/clusters/door-lock/chef-lock-manager.h new file mode 100644 index 00000000000000..709f13c1addc66 --- /dev/null +++ b/examples/chef/common/clusters/door-lock/chef-lock-manager.h @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2020-2024 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. + */ + +#pragma once + +#include "chef-lock-endpoint.h" +#include +#include + +#include + +class LockManager +{ +public: + LockManager() = default; + + bool InitEndpoint(chip::EndpointId endpointId); + + bool SetDoorState(chip::EndpointId endpointId, DoorStateEnum doorState); + + bool SendLockAlarm(chip::EndpointId endpointId, AlarmCodeEnum alarmCode); + + bool Lock(chip::EndpointId endpointId, const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + bool Unlock(chip::EndpointId endpointId, const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + bool Unbolt(chip::EndpointId endpointId, const Nullable & fabricIdx, const Nullable & nodeId, + const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource); + + bool GetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user); + bool SetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, + CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials); + + bool GetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo & credential); + + bool SetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, + DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, + const chip::ByteSpan & credentialData); + + DlStatus GetSchedule(chip::EndpointId endpointId, uint8_t weekDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule & schedule); + DlStatus GetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule & schedule); + DlStatus GetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule & schedule); + + DlStatus SetSchedule(chip::EndpointId endpointId, uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, + DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute); + DlStatus SetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime); + DlStatus SetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime, OperatingModeEnum operatingMode); + + static LockManager & Instance(); + +private: + LockEndpoint * getEndpoint(chip::EndpointId endpointId); + + std::vector mEndpoints; + + static LockManager instance; +}; diff --git a/examples/chef/common/stubs.cpp b/examples/chef/common/stubs.cpp index 48c48620c98fb0..91a7b5a84d10e1 100644 --- a/examples/chef/common/stubs.cpp +++ b/examples/chef/common/stubs.cpp @@ -111,238 +111,6 @@ Protocols::InteractionModel::Status emberAfExternalAttributeWriteCallback(Endpoi return Protocols::InteractionModel::Status::Success; } -// Include door lock callbacks only when the server is enabled -#ifdef MATTER_DM_PLUGIN_DOOR_LOCK_SERVER -#include - -class LockManager -{ -public: - static constexpr uint32_t kNumEndpoints = 1; - static constexpr uint32_t kNumUsersPerEndpoint = 2; - static constexpr uint32_t kNumCredentialsPerEndpoint = 20; - static constexpr uint32_t kNumCredentialsPerUser = 10; - static constexpr uint32_t kMaxNameLength = 32; - static constexpr uint32_t kMaxDataLength = 16; - - struct Credential - { - bool set(DlCredentialStatus status, CredentialTypeEnum type, chip::ByteSpan newData) - { - if (newData.size() > kMaxDataLength || type != CredentialTypeEnum::kPin) - return false; - memcpy(data, newData.data(), newData.size()); - info = EmberAfPluginDoorLockCredentialInfo{ - status, - type, - chip::ByteSpan(data, newData.size()), - }; - return true; - } - - EmberAfPluginDoorLockCredentialInfo info = { DlCredentialStatus::kAvailable }; - uint8_t data[kMaxDataLength]; - }; - - struct User - { - void set(chip::CharSpan newName, uint32_t userId, UserStatusEnum userStatus, UserTypeEnum type, - CredentialRuleEnum credentialRule) - { - size_t sz = std::min(sizeof(name), newName.size()); - memcpy(name, newName.data(), sz); - info = EmberAfPluginDoorLockUserInfo{ - chip::CharSpan(name, sz), chip::Span(), userId, userStatus, type, credentialRule, - }; - } - bool addCredential(CredentialTypeEnum type, uint16_t index) - { - if (info.credentials.size() == kNumCredentialsPerUser) - return false; - auto & cr = credentialMap[info.credentials.size()]; - cr.credentialType = type; - cr.credentialIndex = index; - info.credentials = chip::Span(credentialMap, info.credentials.size() + 1); - return true; - } - - EmberAfPluginDoorLockUserInfo info = { .userStatus = UserStatusEnum::kAvailable }; - char name[kMaxNameLength]; - CredentialStruct credentialMap[kNumCredentialsPerUser]; - }; - - struct Endpoint - { - chip::EndpointId id; - User users[kNumUsersPerEndpoint]; - Credential credentials[kNumCredentialsPerEndpoint]; - }; - - static LockManager & Instance() - { - static LockManager instance; - return instance; - } - - LockManager() { defaultInitialize(); } - - bool getUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) - { - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - if (userIndex >= kNumUsersPerEndpoint) - return false; - user = ep->users[userIndex].info; - return true; - } - - bool setUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, - const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, - CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) - { - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - if (userIndex >= kNumUsersPerEndpoint || totalCredentials > kNumCredentialsPerUser) - return false; - ep->users[userIndex].set(userName, uniqueId, userStatus, usertype, credentialRule); - ep->users[userIndex].info.creationSource = DlAssetSource::kMatterIM; - ep->users[userIndex].info.createdBy = creator; - ep->users[userIndex].info.modificationSource = DlAssetSource::kMatterIM; - ep->users[userIndex].info.lastModifiedBy = modifier; - for (size_t i = 0; i < totalCredentials; i++) - ep->users[userIndex].addCredential(credentials[i].credentialType, credentials[i].credentialIndex); - return true; - } - - bool getCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, - EmberAfPluginDoorLockCredentialInfo & credential) - { - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - if (credentialIndex >= kNumCredentialsPerEndpoint) - return false; - if (credentialType != CredentialTypeEnum::kPin) - return false; - credential = ep->credentials[credentialIndex].info; - return true; - } - - bool setCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, - DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, - const chip::ByteSpan & credentialData) - { - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - if (credentialIndex >= kNumCredentialsPerEndpoint) - return false; - if (credentialType != CredentialTypeEnum::kPin) - return false; - auto & credential = ep->credentials[credentialIndex]; - if (!credential.set(credentialStatus, credentialType, credentialData)) - return false; - credential.info.creationSource = DlAssetSource::kMatterIM; - credential.info.createdBy = creator; - credential.info.modificationSource = DlAssetSource::kMatterIM; - credential.info.lastModifiedBy = modifier; - return true; - } - - bool checkPin(chip::EndpointId endpointId, const chip::Optional & pinCode, - chip::app::Clusters::DoorLock::OperationErrorEnum & err) - { - if (!pinCode.HasValue()) - { - err = OperationErrorEnum::kInvalidCredential; - return false; - } - auto ep = findEndpoint(endpointId); - if (!ep) - return false; - for (auto & pin : ep->credentials) - { - if (pin.info.status == DlCredentialStatus::kOccupied && pin.info.credentialData.data_equal(pinCode.Value())) - { - return true; - } - } - err = OperationErrorEnum::kInvalidCredential; - return false; - } - -private: - Endpoint * findEndpoint(chip::EndpointId endpointId) - { - for (auto & e : endpoints) - { - if (e.id == endpointId) - return &e; - } - return nullptr; - } - - void defaultInitialize() - { - endpoints[0].id = 1; - uint8_t pin[6] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; - endpoints[0].credentials[0].set(DlCredentialStatus::kOccupied, CredentialTypeEnum::kPin, chip::ByteSpan(pin)); - endpoints[0].users[0].set("default"_span, 1, UserStatusEnum::kOccupiedEnabled, UserTypeEnum::kUnrestrictedUser, - CredentialRuleEnum::kSingle); - endpoints[0].users[0].addCredential(CredentialTypeEnum::kPin, 1); - } - - Endpoint endpoints[kNumEndpoints]; -}; - -bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, - const Nullable & nodeId, const chip::Optional & pinCode, - chip::app::Clusters::DoorLock::OperationErrorEnum & err) -{ - err = OperationErrorEnum::kUnspecified; - return DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kLocked); -} - -bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, - const Nullable & nodeId, const chip::Optional & pinCode, - chip::app::Clusters::DoorLock::OperationErrorEnum & err) -{ - err = OperationErrorEnum::kUnspecified; - return DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kUnlocked); -} - -bool emberAfPluginDoorLockGetUser(chip::EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) -{ - return LockManager::Instance().getUser(endpointId, userIndex - 1, user); -} - -bool emberAfPluginDoorLockSetUser(chip::EndpointId endpointId, uint16_t userIndex, chip::FabricIndex creator, - chip::FabricIndex modifier, const chip::CharSpan & userName, uint32_t uniqueId, - UserStatusEnum userStatus, UserTypeEnum usertype, CredentialRuleEnum credentialRule, - const CredentialStruct * credentials, size_t totalCredentials) -{ - return LockManager::Instance().setUser(endpointId, userIndex - 1, creator, modifier, userName, uniqueId, userStatus, usertype, - credentialRule, credentials, totalCredentials); -} - -bool emberAfPluginDoorLockGetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, CredentialTypeEnum credentialType, - EmberAfPluginDoorLockCredentialInfo & credential) -{ - return LockManager::Instance().getCredential(endpointId, credentialIndex - 1, credentialType, credential); -} - -bool emberAfPluginDoorLockSetCredential(chip::EndpointId endpointId, uint16_t credentialIndex, chip::FabricIndex creator, - chip::FabricIndex modifier, DlCredentialStatus credentialStatus, - CredentialTypeEnum credentialType, const chip::ByteSpan & credentialData) -{ - return LockManager::Instance().setCredential(endpointId, credentialIndex - 1, creator, modifier, credentialStatus, - credentialType, credentialData); -} - -#endif /* MATTER_DM_PLUGIN_DOOR_LOCK_SERVER */ - void emberAfPluginSmokeCoAlarmSelfTestRequestCommand(EndpointId endpointId) {} void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & attributePath, uint8_t type, uint16_t size, diff --git a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter index 5819641c778086..bceebc611c7ca3 100644 --- a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter +++ b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.matter @@ -51,83 +51,6 @@ cluster Identify = 3 { command access(invoke: manage) TriggerEffect(TriggerEffectRequest): DefaultSuccess = 64; } -/** Attributes and commands for group configuration and manipulation. */ -cluster Groups = 4 { - revision 4; - - bitmap Feature : bitmap32 { - kGroupNames = 0x1; - } - - bitmap NameSupportBitmap : bitmap8 { - kGroupNames = 0x80; - } - - readonly attribute NameSupportBitmap nameSupport = 0; - readonly attribute command_id generatedCommandList[] = 65528; - readonly attribute command_id acceptedCommandList[] = 65529; - readonly attribute event_id eventList[] = 65530; - readonly attribute attrib_id attributeList[] = 65531; - readonly attribute bitmap32 featureMap = 65532; - readonly attribute int16u clusterRevision = 65533; - - request struct AddGroupRequest { - group_id groupID = 0; - char_string<16> groupName = 1; - } - - response struct AddGroupResponse = 0 { - enum8 status = 0; - group_id groupID = 1; - } - - request struct ViewGroupRequest { - group_id groupID = 0; - } - - response struct ViewGroupResponse = 1 { - enum8 status = 0; - group_id groupID = 1; - char_string<16> groupName = 2; - } - - request struct GetGroupMembershipRequest { - group_id groupList[] = 0; - } - - response struct GetGroupMembershipResponse = 2 { - nullable int8u capacity = 0; - group_id groupList[] = 1; - } - - request struct RemoveGroupRequest { - group_id groupID = 0; - } - - response struct RemoveGroupResponse = 3 { - enum8 status = 0; - group_id groupID = 1; - } - - request struct AddGroupIfIdentifyingRequest { - group_id groupID = 0; - char_string<16> groupName = 1; - } - - /** Command description for AddGroup */ - fabric command access(invoke: manage) AddGroup(AddGroupRequest): AddGroupResponse = 0; - /** Command description for ViewGroup */ - fabric command ViewGroup(ViewGroupRequest): ViewGroupResponse = 1; - /** Command description for GetGroupMembership */ - fabric command GetGroupMembership(GetGroupMembershipRequest): GetGroupMembershipResponse = 2; - /** Command description for RemoveGroup */ - fabric command access(invoke: manage) RemoveGroup(RemoveGroupRequest): RemoveGroupResponse = 3; - /** Command description for RemoveAllGroups */ - fabric command access(invoke: manage) RemoveAllGroups(): DefaultSuccess = 4; - /** Command description for AddGroupIfIdentifying */ - fabric command access(invoke: manage) AddGroupIfIdentifying(AddGroupIfIdentifyingRequest): DefaultSuccess = 5; -} - /** The Descriptor Cluster is meant to replace the support from the Zigbee Device Object (ZDO) for describing a node, its endpoints and clusters. */ cluster Descriptor = 29 { revision 2; @@ -161,27 +84,6 @@ cluster Descriptor = 29 { readonly attribute int16u clusterRevision = 65533; } -/** The Binding Cluster is meant to replace the support from the Zigbee Device Object (ZDO) for supporting the binding table. */ -cluster Binding = 30 { - revision 1; // NOTE: Default/not specifically set - - fabric_scoped struct TargetStruct { - optional node_id node = 1; - optional group_id group = 2; - optional endpoint_no endpoint = 3; - optional cluster_id cluster = 4; - fabric_idx fabricIndex = 254; - } - - attribute access(write: manage) TargetStruct binding[] = 0; - readonly attribute command_id generatedCommandList[] = 65528; - readonly attribute command_id acceptedCommandList[] = 65529; - readonly attribute event_id eventList[] = 65530; - readonly attribute attrib_id attributeList[] = 65531; - readonly attribute bitmap32 featureMap = 65532; - readonly attribute int16u clusterRevision = 65533; -} - /** The Access Control Cluster exposes a data model view of a Node's Access Control List (ACL), which codifies the rules used to manage and enforce Access Control for the Node's endpoints and their associated @@ -569,6 +471,265 @@ cluster TimeFormatLocalization = 44 { readonly attribute int16u clusterRevision = 65533; } +/** This cluster is used to describe the configuration and capabilities of a physical power source that provides power to the Node. */ +cluster PowerSource = 47 { + revision 1; // NOTE: Default/not specifically set + + enum BatApprovedChemistryEnum : enum16 { + kUnspecified = 0; + kAlkaline = 1; + kLithiumCarbonFluoride = 2; + kLithiumChromiumOxide = 3; + kLithiumCopperOxide = 4; + kLithiumIronDisulfide = 5; + kLithiumManganeseDioxide = 6; + kLithiumThionylChloride = 7; + kMagnesium = 8; + kMercuryOxide = 9; + kNickelOxyhydride = 10; + kSilverOxide = 11; + kZincAir = 12; + kZincCarbon = 13; + kZincChloride = 14; + kZincManganeseDioxide = 15; + kLeadAcid = 16; + kLithiumCobaltOxide = 17; + kLithiumIon = 18; + kLithiumIonPolymer = 19; + kLithiumIronPhosphate = 20; + kLithiumSulfur = 21; + kLithiumTitanate = 22; + kNickelCadmium = 23; + kNickelHydrogen = 24; + kNickelIron = 25; + kNickelMetalHydride = 26; + kNickelZinc = 27; + kSilverZinc = 28; + kSodiumIon = 29; + kSodiumSulfur = 30; + kZincBromide = 31; + kZincCerium = 32; + } + + enum BatChargeFaultEnum : enum8 { + kUnspecified = 0; + kAmbientTooHot = 1; + kAmbientTooCold = 2; + kBatteryTooHot = 3; + kBatteryTooCold = 4; + kBatteryAbsent = 5; + kBatteryOverVoltage = 6; + kBatteryUnderVoltage = 7; + kChargerOverVoltage = 8; + kChargerUnderVoltage = 9; + kSafetyTimeout = 10; + } + + enum BatChargeLevelEnum : enum8 { + kOK = 0; + kWarning = 1; + kCritical = 2; + } + + enum BatChargeStateEnum : enum8 { + kUnknown = 0; + kIsCharging = 1; + kIsAtFullCharge = 2; + kIsNotCharging = 3; + } + + enum BatCommonDesignationEnum : enum16 { + kUnspecified = 0; + kAAA = 1; + kAA = 2; + kC = 3; + kD = 4; + k4v5 = 5; + k6v0 = 6; + k9v0 = 7; + k12AA = 8; + kAAAA = 9; + kA = 10; + kB = 11; + kF = 12; + kN = 13; + kNo6 = 14; + kSubC = 15; + kA23 = 16; + kA27 = 17; + kBA5800 = 18; + kDuplex = 19; + k4SR44 = 20; + k523 = 21; + k531 = 22; + k15v0 = 23; + k22v5 = 24; + k30v0 = 25; + k45v0 = 26; + k67v5 = 27; + kJ = 28; + kCR123A = 29; + kCR2 = 30; + k2CR5 = 31; + kCRP2 = 32; + kCRV3 = 33; + kSR41 = 34; + kSR43 = 35; + kSR44 = 36; + kSR45 = 37; + kSR48 = 38; + kSR54 = 39; + kSR55 = 40; + kSR57 = 41; + kSR58 = 42; + kSR59 = 43; + kSR60 = 44; + kSR63 = 45; + kSR64 = 46; + kSR65 = 47; + kSR66 = 48; + kSR67 = 49; + kSR68 = 50; + kSR69 = 51; + kSR516 = 52; + kSR731 = 53; + kSR712 = 54; + kLR932 = 55; + kA5 = 56; + kA10 = 57; + kA13 = 58; + kA312 = 59; + kA675 = 60; + kAC41E = 61; + k10180 = 62; + k10280 = 63; + k10440 = 64; + k14250 = 65; + k14430 = 66; + k14500 = 67; + k14650 = 68; + k15270 = 69; + k16340 = 70; + kRCR123A = 71; + k17500 = 72; + k17670 = 73; + k18350 = 74; + k18500 = 75; + k18650 = 76; + k19670 = 77; + k25500 = 78; + k26650 = 79; + k32600 = 80; + } + + enum BatFaultEnum : enum8 { + kUnspecified = 0; + kOverTemp = 1; + kUnderTemp = 2; + } + + enum BatReplaceabilityEnum : enum8 { + kUnspecified = 0; + kNotReplaceable = 1; + kUserReplaceable = 2; + kFactoryReplaceable = 3; + } + + enum PowerSourceStatusEnum : enum8 { + kUnspecified = 0; + kActive = 1; + kStandby = 2; + kUnavailable = 3; + } + + enum WiredCurrentTypeEnum : enum8 { + kAC = 0; + kDC = 1; + } + + enum WiredFaultEnum : enum8 { + kUnspecified = 0; + kOverVoltage = 1; + kUnderVoltage = 2; + } + + bitmap Feature : bitmap32 { + kWired = 0x1; + kBattery = 0x2; + kRechargeable = 0x4; + kReplaceable = 0x8; + } + + struct BatChargeFaultChangeType { + BatChargeFaultEnum current[] = 0; + BatChargeFaultEnum previous[] = 1; + } + + struct BatFaultChangeType { + BatFaultEnum current[] = 0; + BatFaultEnum previous[] = 1; + } + + struct WiredFaultChangeType { + WiredFaultEnum current[] = 0; + WiredFaultEnum previous[] = 1; + } + + info event WiredFaultChange = 0 { + WiredFaultEnum current[] = 0; + WiredFaultEnum previous[] = 1; + } + + info event BatFaultChange = 1 { + BatFaultEnum current[] = 0; + BatFaultEnum previous[] = 1; + } + + info event BatChargeFaultChange = 2 { + BatChargeFaultEnum current[] = 0; + BatChargeFaultEnum previous[] = 1; + } + + readonly attribute PowerSourceStatusEnum status = 0; + readonly attribute int8u order = 1; + readonly attribute char_string<60> description = 2; + readonly attribute optional nullable int32u wiredAssessedInputVoltage = 3; + readonly attribute optional nullable int16u wiredAssessedInputFrequency = 4; + readonly attribute optional WiredCurrentTypeEnum wiredCurrentType = 5; + readonly attribute optional nullable int32u wiredAssessedCurrent = 6; + readonly attribute optional int32u wiredNominalVoltage = 7; + readonly attribute optional int32u wiredMaximumCurrent = 8; + readonly attribute optional boolean wiredPresent = 9; + readonly attribute optional WiredFaultEnum activeWiredFaults[] = 10; + readonly attribute optional nullable int32u batVoltage = 11; + readonly attribute optional nullable int8u batPercentRemaining = 12; + readonly attribute optional nullable int32u batTimeRemaining = 13; + readonly attribute optional BatChargeLevelEnum batChargeLevel = 14; + readonly attribute optional boolean batReplacementNeeded = 15; + readonly attribute optional BatReplaceabilityEnum batReplaceability = 16; + readonly attribute optional boolean batPresent = 17; + readonly attribute optional BatFaultEnum activeBatFaults[] = 18; + readonly attribute optional char_string<60> batReplacementDescription = 19; + readonly attribute optional BatCommonDesignationEnum batCommonDesignation = 20; + readonly attribute optional char_string<20> batANSIDesignation = 21; + readonly attribute optional char_string<20> batIECDesignation = 22; + readonly attribute optional BatApprovedChemistryEnum batApprovedChemistry = 23; + readonly attribute optional int32u batCapacity = 24; + readonly attribute optional int8u batQuantity = 25; + readonly attribute optional BatChargeStateEnum batChargeState = 26; + readonly attribute optional nullable int32u batTimeToFullCharge = 27; + readonly attribute optional boolean batFunctionalWhileCharging = 28; + readonly attribute optional nullable int32u batChargingCurrent = 29; + readonly attribute optional BatChargeFaultEnum activeBatChargeFaults[] = 30; + readonly attribute endpoint_no endpointList[] = 31; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + /** This cluster is used to manage global aspects of the Commissioning flow. */ cluster GeneralCommissioning = 48 { revision 1; // NOTE: Default/not specifically set @@ -2178,40 +2339,22 @@ endpoint 0 { } } endpoint 1 { + device type ma_powersource = 17, version 1; device type ma_doorlock = 10, version 1; - binding cluster Binding; server cluster Identify { ram attribute identifyTime default = 0x0; ram attribute identifyType default = 0x0; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 2; handle command Identify; - } - - server cluster Groups { - ram attribute nameSupport default = 0; - callback attribute generatedCommandList; - callback attribute acceptedCommandList; - callback attribute attributeList; - ram attribute featureMap default = 0; - ram attribute clusterRevision default = 3; - - handle command AddGroup; - handle command AddGroupResponse; - handle command ViewGroup; - handle command ViewGroupResponse; - handle command GetGroupMembership; - handle command GetGroupMembershipResponse; - handle command RemoveGroup; - handle command RemoveGroupResponse; - handle command RemoveAllGroups; - handle command AddGroupIfIdentifying; + handle command TriggerEffect; } server cluster Descriptor { @@ -2221,39 +2364,90 @@ endpoint 1 { callback attribute partsList; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; callback attribute featureMap; callback attribute clusterRevision; } + server cluster PowerSource { + ram attribute status default = 1; + ram attribute order default = 1; + ram attribute description default = "Battery"; + ram attribute batVoltage; + ram attribute batPercentRemaining; + ram attribute batTimeRemaining; + ram attribute batChargeLevel; + ram attribute batReplacementNeeded; + ram attribute batReplaceability; + ram attribute batPresent; + ram attribute batReplacementDescription; + ram attribute batQuantity default = 1; + callback attribute endpointList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0x0A; + ram attribute clusterRevision default = 2; + } + server cluster DoorLock { emits event DoorLockAlarm; + emits event DoorStateChange; emits event LockOperation; emits event LockOperationError; + emits event LockUserChange; ram attribute lockState default = 1; ram attribute lockType default = 0; ram attribute actuatorEnabled default = 0; - ram attribute numberOfTotalUsersSupported default = 2; - ram attribute numberOfPINUsersSupported default = 2; - ram attribute maxPINCodeLength default = 10; - ram attribute minPINCodeLength default = 5; + ram attribute doorState default = 1; + ram attribute doorOpenEvents; + ram attribute doorClosedEvents; + ram attribute numberOfTotalUsersSupported default = 10; + ram attribute numberOfPINUsersSupported default = 10; + ram attribute numberOfRFIDUsersSupported default = 10; + ram attribute numberOfWeekDaySchedulesSupportedPerUser default = 10; + ram attribute numberOfYearDaySchedulesSupportedPerUser default = 10; + ram attribute numberOfHolidaySchedulesSupported default = 10; + ram attribute maxPINCodeLength default = 8; + ram attribute minPINCodeLength default = 6; + ram attribute maxRFIDCodeLength default = 20; + ram attribute minRFIDCodeLength default = 10; + ram attribute credentialRulesSupport default = 1; ram attribute numberOfCredentialsSupportedPerUser default = 5; - ram attribute autoRelockTime default = 0; + ram attribute language default = "en"; + ram attribute autoRelockTime default = 5; + ram attribute soundVolume default = 0; ram attribute operatingMode default = 0; - ram attribute supportedOperatingModes default = 0xFFF6; + ram attribute supportedOperatingModes default = 0xFFFF; + ram attribute enableOneTouchLocking default = 0; + ram attribute enablePrivacyModeButton default = 0; ram attribute wrongCodeEntryLimit default = 3; ram attribute userCodeTemporaryDisableTime default = 10; - ram attribute sendPINOverTheAir default = 0; - ram attribute requirePINforRemoteOperation default = 1; + ram attribute requirePINforRemoteOperation default = 0; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; - ram attribute featureMap default = 0x0181; - ram attribute clusterRevision default = 6; + ram attribute featureMap default = 0x1DB3; + ram attribute clusterRevision default = 7; handle command LockDoor; handle command UnlockDoor; handle command UnlockWithTimeout; + handle command SetWeekDaySchedule; + handle command GetWeekDaySchedule; + handle command GetWeekDayScheduleResponse; + handle command ClearWeekDaySchedule; + handle command SetYearDaySchedule; + handle command GetYearDaySchedule; + handle command GetYearDayScheduleResponse; + handle command ClearYearDaySchedule; + handle command SetHolidaySchedule; + handle command GetHolidaySchedule; + handle command GetHolidayScheduleResponse; + handle command ClearHolidaySchedule; handle command SetUser; handle command GetUser; handle command GetUserResponse; @@ -2263,6 +2457,7 @@ endpoint 1 { handle command GetCredentialStatus; handle command GetCredentialStatusResponse; handle command ClearCredential; + handle command UnboltDoor; } } diff --git a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap index 66f7adbb12fd02..1e9094f9d7a96e 100644 --- a/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap +++ b/examples/chef/devices/rootnode_doorlock_aNKYAreMXE.zap @@ -2480,13 +2480,21 @@ "profileId": 259, "label": "MA-doorlock", "name": "MA-doorlock" + }, + { + "code": 17, + "profileId": 259, + "label": "MA-powersource", + "name": "MA-powersource" } ], "deviceVersions": [ + 1, 1 ], "deviceIdentifiers": [ - 10 + 10, + 17 ], "deviceTypeName": "MA-doorlock", "deviceTypeCode": 10, @@ -2507,6 +2515,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -2574,6 +2590,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AttributeList", "code": 65531, @@ -2625,106 +2657,72 @@ ] }, { - "name": "Groups", - "code": 4, + "name": "Descriptor", + "code": 29, "mfgCode": null, - "define": "GROUPS_CLUSTER", + "define": "DESCRIPTOR_CLUSTER", "side": "server", "enabled": 1, - "commands": [ - { - "name": "AddGroup", - "code": 0, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 - }, + "attributes": [ { - "name": "AddGroupResponse", + "name": "DeviceTypeList", "code": 0, "mfgCode": null, - "source": "server", - "isIncoming": 0, - "isEnabled": 1 - }, - { - "name": "ViewGroup", - "code": 1, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 }, { - "name": "ViewGroupResponse", + "name": "ServerList", "code": 1, "mfgCode": null, - "source": "server", - "isIncoming": 0, - "isEnabled": 1 - }, - { - "name": "GetGroupMembership", - "code": 2, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 }, { - "name": "GetGroupMembershipResponse", + "name": "ClientList", "code": 2, "mfgCode": null, - "source": "server", - "isIncoming": 0, - "isEnabled": 1 - }, - { - "name": "RemoveGroup", - "code": 3, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 }, { - "name": "RemoveGroupResponse", + "name": "PartsList", "code": 3, "mfgCode": null, - "source": "server", - "isIncoming": 0, - "isEnabled": 1 - }, - { - "name": "RemoveAllGroups", - "code": 4, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 - }, - { - "name": "AddGroupIfIdentifying", - "code": 5, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 - } - ], - "attributes": [ - { - "name": "NameSupport", - "code": 0, - "mfgCode": null, "side": "server", - "type": "NameSupportBitmap", + "type": "array", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2762,6 +2760,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AttributeList", "code": 65531, @@ -2785,10 +2799,10 @@ "side": "server", "type": "bitmap32", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2801,10 +2815,10 @@ "side": "server", "type": "int16u", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "3", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2813,53 +2827,69 @@ ] }, { - "name": "Descriptor", - "code": 29, + "name": "Power Source", + "code": 47, "mfgCode": null, - "define": "DESCRIPTOR_CLUSTER", + "define": "POWER_SOURCE_CLUSTER", "side": "server", "enabled": 1, "attributes": [ { - "name": "DeviceTypeList", + "name": "Status", "code": 0, "mfgCode": null, "side": "server", - "type": "array", + "type": "PowerSourceStatusEnum", "included": 1, - "storageOption": "External", + "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "ServerList", + "name": "Order", "code": 1, "mfgCode": null, "side": "server", - "type": "array", + "type": "int8u", "included": 1, - "storageOption": "External", + "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "ClientList", + "name": "Description", "code": 2, "mfgCode": null, "side": "server", - "type": "array", + "type": "char_string", "included": 1, - "storageOption": "External", + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "Battery", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatVoltage", + "code": 11, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", "singleton": 0, "bounded": 0, "defaultValue": null, @@ -2869,8 +2899,136 @@ "reportableChange": 0 }, { - "name": "PartsList", - "code": 3, + "name": "BatPercentRemaining", + "code": 12, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatTimeRemaining", + "code": 13, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatChargeLevel", + "code": 14, + "mfgCode": null, + "side": "server", + "type": "BatChargeLevelEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatReplacementNeeded", + "code": 15, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatReplaceability", + "code": 16, + "mfgCode": null, + "side": "server", + "type": "BatReplaceabilityEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatPresent", + "code": 17, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatReplacementDescription", + "code": 19, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BatQuantity", + "code": 25, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EndpointList", + "code": 31, "mfgCode": null, "side": "server", "type": "array", @@ -2917,8 +3075,8 @@ "reportableChange": 0 }, { - "name": "AttributeList", - "code": 65531, + "name": "EventList", + "code": 65530, "mfgCode": null, "side": "server", "type": "array", @@ -2933,11 +3091,11 @@ "reportableChange": 0 }, { - "name": "FeatureMap", - "code": 65532, + "name": "AttributeList", + "code": 65531, "mfgCode": null, "side": "server", - "type": "bitmap32", + "type": "array", "included": 1, "storageOption": "External", "singleton": 0, @@ -2949,42 +3107,32 @@ "reportableChange": 0 }, { - "name": "ClusterRevision", - "code": 65533, + "name": "FeatureMap", + "code": 65532, "mfgCode": null, "side": "server", - "type": "int16u", + "type": "bitmap32", "included": 1, - "storageOption": "External", + "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "0x0A", "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 - } - ] - }, - { - "name": "Binding", - "code": 30, - "mfgCode": null, - "define": "BINDING_CLUSTER", - "side": "client", - "enabled": 1, - "attributes": [ + }, { "name": "ClusterRevision", "code": 65533, "mfgCode": null, - "side": "client", + "side": "server", "type": "int16u", "included": 1, "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": "2", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3009,16 +3157,112 @@ "isEnabled": 1 }, { - "name": "UnlockDoor", - "code": 1, + "name": "UnlockDoor", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "UnlockWithTimeout", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetWeekDaySchedule", + "code": 11, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetWeekDaySchedule", + "code": 12, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetWeekDayScheduleResponse", + "code": 12, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ClearWeekDaySchedule", + "code": 13, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetYearDaySchedule", + "code": 14, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetYearDaySchedule", + "code": 15, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetYearDayScheduleResponse", + "code": 15, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ClearYearDaySchedule", + "code": 16, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetHolidaySchedule", + "code": 17, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetHolidaySchedule", + "code": 18, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetHolidayScheduleResponse", + "code": 18, "mfgCode": null, - "source": "client", - "isIncoming": 1, + "source": "server", + "isIncoming": 0, "isEnabled": 1 }, { - "name": "UnlockWithTimeout", - "code": 3, + "name": "ClearHolidaySchedule", + "code": 19, "mfgCode": null, "source": "client", "isIncoming": 1, @@ -3095,6 +3339,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "UnboltDoor", + "code": 39, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -3146,6 +3398,54 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "DoorState", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "DoorStateEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DoorOpenEvents", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "DoorClosedEvents", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "NumberOfTotalUsersSupported", "code": 17, @@ -3156,7 +3456,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "2", + "defaultValue": "10", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3172,7 +3472,71 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "2", + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfRFIDUsersSupported", + "code": 19, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfWeekDaySchedulesSupportedPerUser", + "code": 20, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfYearDaySchedulesSupportedPerUser", + "code": 21, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfHolidaySchedulesSupported", + "code": 22, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3188,7 +3552,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "10", + "defaultValue": "8", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3204,7 +3568,55 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "5", + "defaultValue": "6", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxRFIDCodeLength", + "code": 25, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "20", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MinRFIDCodeLength", + "code": 26, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CredentialRulesSupport", + "code": 27, + "mfgCode": null, + "side": "server", + "type": "DlCredentialRuleMask", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3226,6 +3638,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "Language", + "code": 33, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "en", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AutoRelockTime", "code": 35, @@ -3236,6 +3664,22 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SoundVolume", + "code": 36, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, "defaultValue": "0", "reportable": 1, "minInterval": 1, @@ -3268,7 +3712,39 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0xFFF6", + "defaultValue": "0xFFFF", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EnableOneTouchLocking", + "code": 41, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EnablePrivacyModeButton", + "code": 43, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3307,8 +3783,8 @@ "reportableChange": 0 }, { - "name": "SendPINOverTheAir", - "code": 50, + "name": "RequirePINforRemoteOperation", + "code": 51, "mfgCode": null, "side": "server", "type": "boolean", @@ -3323,24 +3799,24 @@ "reportableChange": 0 }, { - "name": "RequirePINforRemoteOperation", - "code": 51, + "name": "GeneratedCommandList", + "code": 65528, "mfgCode": null, "side": "server", - "type": "boolean", + "type": "array", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "GeneratedCommandList", - "code": 65528, + "name": "AcceptedCommandList", + "code": 65529, "mfgCode": null, "side": "server", "type": "array", @@ -3355,8 +3831,8 @@ "reportableChange": 0 }, { - "name": "AcceptedCommandList", - "code": 65529, + "name": "EventList", + "code": 65530, "mfgCode": null, "side": "server", "type": "array", @@ -3396,7 +3872,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x0181", + "defaultValue": "0x1DB3", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3412,7 +3888,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "6", + "defaultValue": "7", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3427,6 +3903,13 @@ "side": "server", "included": 1 }, + { + "name": "DoorStateChange", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, { "name": "LockOperation", "code": 2, @@ -3440,6 +3923,13 @@ "mfgCode": null, "side": "server", "included": 1 + }, + { + "name": "LockUserChange", + "code": 4, + "mfgCode": null, + "side": "server", + "included": 1 } ] } @@ -3458,10 +3948,10 @@ { "endpointTypeName": "Anonymous Endpoint Type", "endpointTypeIndex": 1, - "profileId": 260, + "profileId": 259, "endpointId": 1, "networkId": 0, "parentEndpointIdentifier": null } ] -} \ No newline at end of file +} diff --git a/examples/chef/esp32/main/CMakeLists.txt b/examples/chef/esp32/main/CMakeLists.txt index 8278cf9a735675..75cf0b85222d95 100644 --- a/examples/chef/esp32/main/CMakeLists.txt +++ b/examples/chef/esp32/main/CMakeLists.txt @@ -63,14 +63,15 @@ set(SRC_DIRS_LIST ${SRC_DIRS_LIST} "${CMAKE_CURRENT_LIST_DIR}" "${CMAKE_SOURCE_DIR}/../common" - "${CMAKE_SOURCE_DIR}/../common/clusters/media-input/" + "${CMAKE_SOURCE_DIR}/../common/clusters/audio-output/" + "${CMAKE_SOURCE_DIR}/../common/clusters/channel/" + "${CMAKE_SOURCE_DIR}/../common/clusters/door-lock/" + "${CMAKE_SOURCE_DIR}/../common/clusters/keypad-input/" "${CMAKE_SOURCE_DIR}/../common/clusters/low-power/" + "${CMAKE_SOURCE_DIR}/../common/clusters/media-input/" "${CMAKE_SOURCE_DIR}/../common/clusters/media-playback/" "${CMAKE_SOURCE_DIR}/../common/clusters/target-navigator/" "${CMAKE_SOURCE_DIR}/../common/clusters/wake-on-lan/" - "${CMAKE_SOURCE_DIR}/../common/clusters/channel/" - "${CMAKE_SOURCE_DIR}/../common/clusters/keypad-input/" - "${CMAKE_SOURCE_DIR}/../common/clusters/audio-output/" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/zzz_generated/app-common/app-common/zap-generated/attributes" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/util" diff --git a/examples/chef/linux/BUILD.gn b/examples/chef/linux/BUILD.gn index 0a4e2385f28dda..3cf4bdc7dd5d98 100644 --- a/examples/chef/linux/BUILD.gn +++ b/examples/chef/linux/BUILD.gn @@ -49,6 +49,9 @@ executable("${sample_name}") { "${project_dir}/common/chef-rvc-operational-state-delegate.cpp", "${project_dir}/common/clusters/audio-output/AudioOutputManager.cpp", "${project_dir}/common/clusters/channel/ChannelManager.cpp", + "${project_dir}/common/clusters/door-lock/chef-doorlock-stubs.cpp", + "${project_dir}/common/clusters/door-lock/chef-lock-endpoint.cpp", + "${project_dir}/common/clusters/door-lock/chef-lock-manager.cpp", "${project_dir}/common/clusters/keypad-input/KeypadInputManager.cpp", "${project_dir}/common/clusters/low-power/LowPowerManager.cpp", "${project_dir}/common/clusters/media-input/MediaInputManager.cpp", diff --git a/examples/chef/nrfconnect/CMakeLists.txt b/examples/chef/nrfconnect/CMakeLists.txt index 25d663211b9f15..29b21817705afa 100644 --- a/examples/chef/nrfconnect/CMakeLists.txt +++ b/examples/chef/nrfconnect/CMakeLists.txt @@ -88,14 +88,17 @@ target_sources(app PRIVATE ${CHEF}/common/chef-resource-monitoring-delegates.cpp ${CHEF}/common/chef-rvc-mode-delegate.cpp ${CHEF}/common/chef-rvc-operational-state-delegate.cpp - ${CHEF}/common/clusters/media-input/MediaInputManager.cpp + ${CHEF}/common/clusters/audio-output/AudioOutputManager.cpp + ${CHEF}/common/clusters/channel/ChannelManager.cpp + ${CHEF}/common/clusters/door-lock/chef-doorlock-stubs.cpp + ${CHEF}/common/clusters/door-lock/chef-lock-endpoint.cpp + ${CHEF}/common/clusters/door-lock/chef-lock-manager.cpp + ${CHEF}/common/clusters/keypad-input/KeypadInputManager.cpp ${CHEF}/common/clusters/low-power/LowPowerManager.cpp + ${CHEF}/common/clusters/media-input/MediaInputManager.cpp ${CHEF}/common/clusters/media-playback/MediaPlaybackManager.cpp ${CHEF}/common/clusters/target-navigator/TargetNavigatorManager.cpp ${CHEF}/common/clusters/wake-on-lan/WakeOnLanManager.cpp - ${CHEF}/common/clusters/channel/ChannelManager.cpp - ${CHEF}/common/clusters/keypad-input/KeypadInputManager.cpp - ${CHEF}/common/clusters/audio-output/AudioOutputManager.cpp ${CHEF}/common/stubs.cpp ${CHEF}/nrfconnect/main.cpp ) From d86fa60d0fc71a8383771b85af8ab91879780dcb Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Fri, 15 Mar 2024 12:57:32 -0400 Subject: [PATCH 16/27] Set more than one DST offset in MTRDevice if the server supports that. (#32578) This way when the next DST transition happens, the device won't be confused about the local time. --- src/darwin/Framework/CHIP/MTRDevice.mm | 71 ++++++++++--------- .../Framework/CHIP/MTRDeviceController.mm | 51 ++++++------- src/darwin/Framework/CHIP/MTRTimeUtils.h | 34 +++++++++ src/darwin/Framework/CHIP/MTRTimeUtils.mm | 67 +++++++++++++++++ .../Framework/CHIPTests/MTRDSTOffsetTests.m | 57 +++++++++++++++ .../Matter.xcodeproj/project.pbxproj | 12 ++++ 6 files changed, 231 insertions(+), 61 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTRTimeUtils.h create mode 100644 src/darwin/Framework/CHIP/MTRTimeUtils.mm create mode 100644 src/darwin/Framework/CHIPTests/MTRDSTOffsetTests.m diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index a83528b74d53e8..98387bfde797fb 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -33,6 +33,7 @@ #import "MTRError_Internal.h" #import "MTREventTLVValueDecoder_Internal.h" #import "MTRLogging_Internal.h" +#import "MTRTimeUtils.h" #import "MTRUnfairLock.h" #import "zap-generated/MTRCommandPayloads_Internal.h" @@ -270,41 +271,50 @@ - (void)_setTimeOnDevice return; } - NSTimeZone * localTimeZone = [NSTimeZone localTimeZone]; - BOOL setDST = TRUE; - if (!localTimeZone) { - MTR_LOG_ERROR("%@ Could not retrieve local time zone. Unable to setDSTOffset on endpoints.", self); - setDST = FALSE; - } - uint64_t matterEpochTimeMicroseconds = 0; - uint64_t nextDSTInMatterEpochTimeMicroseconds = 0; if (!DateToMatterEpochMicroseconds(now, matterEpochTimeMicroseconds)) { MTR_LOG_ERROR("%@ Could not convert NSDate (%@) to Matter Epoch Time. Unable to setUTCTime on endpoints.", self, now); return; } - int32_t dstOffset = 0; - if (setDST) { - NSTimeInterval dstOffsetAsInterval = [localTimeZone daylightSavingTimeOffsetForDate:now]; - dstOffset = int32_t(dstOffsetAsInterval); - - // Calculate time to next DST. This is needed when we set the current DST. - NSDate * nextDSTTransitionDate = [localTimeZone nextDaylightSavingTimeTransition]; - if (!DateToMatterEpochMicroseconds(nextDSTTransitionDate, nextDSTInMatterEpochTimeMicroseconds)) { - MTR_LOG_ERROR("%@ Could not convert NSDate (%@) to Matter Epoch Time. Unable to setDSTOffset on endpoints.", self, nextDSTTransitionDate); - setDST = FALSE; - } - } - // Set Time on each Endpoint with a Time Synchronization Cluster Server NSArray * endpointsToSync = [self _endpointsWithTimeSyncClusterServer]; for (NSNumber * endpoint in endpointsToSync) { MTR_LOG_DEBUG("%@ Setting Time on Endpoint %@", self, endpoint); [self _setUTCTime:matterEpochTimeMicroseconds withGranularity:MTRTimeSynchronizationGranularityMicrosecondsGranularity forEndpoint:endpoint]; - if (setDST) { - [self _setDSTOffset:dstOffset validStarting:0 validUntil:nextDSTInMatterEpochTimeMicroseconds forEndpoint:endpoint]; + + // Check how many DST offsets this endpoint supports. + auto dstOffsetsMaxSizePath = [MTRAttributePath attributePathWithEndpointID:endpoint clusterID:@(MTRClusterIDTypeTimeSynchronizationID) attributeID:@(MTRAttributeIDTypeClusterTimeSynchronizationAttributeDSTOffsetListMaxSizeID)]; + auto dstOffsetsMaxSize = [self readAttributeWithEndpointID:dstOffsetsMaxSizePath.endpoint clusterID:dstOffsetsMaxSizePath.cluster attributeID:dstOffsetsMaxSizePath.attribute params:nil]; + if (dstOffsetsMaxSize == nil) { + // This endpoint does not support TZ, so won't support SetDSTOffset. + MTR_LOG_DEFAULT("%@ Unable to SetDSTOffset on endpoint %@, since it does not support the TZ feature", self, endpoint); + continue; } + auto attrReport = [[MTRAttributeReport alloc] initWithResponseValue:@{ + MTRAttributePathKey : dstOffsetsMaxSizePath, + MTRDataKey : dstOffsetsMaxSize, + } + error:nil]; + uint8_t maxOffsetCount; + if (attrReport == nil) { + MTR_LOG_ERROR("%@ DSTOffsetListMaxSize value on endpoint %@ is invalid. Defaulting to 1.", self, endpoint); + maxOffsetCount = 1; + } else { + NSNumber * maxOffsetCountAsNumber = attrReport.value; + maxOffsetCount = maxOffsetCountAsNumber.unsignedCharValue; + if (maxOffsetCount == 0) { + MTR_LOG_ERROR("%@ DSTOffsetListMaxSize value on endpoint %@ is 0, which is not allowed. Defaulting to 1.", self, endpoint); + maxOffsetCount = 1; + } + } + auto * dstOffsets = MTRComputeDSTOffsets(maxOffsetCount); + if (dstOffsets == nil) { + MTR_LOG_ERROR("%@ Could not retrieve DST offset information. Unable to setDSTOffset on endpoint %@.", self, endpoint); + continue; + } + + [self _setDSTOffsets:dstOffsets forEndpoint:endpoint]; } } @@ -410,23 +420,18 @@ - (void)_setUTCTime:(UInt64)matterEpochTime withGranularity:(uint8_t)granularity completion:setUTCTimeResponseHandler]; } -- (void)_setDSTOffset:(int32_t)dstOffset validStarting:(uint64_t)validStarting validUntil:(uint64_t)validUntil forEndpoint:(NSNumber *)endpoint +- (void)_setDSTOffsets:(NSArray *)dstOffsets forEndpoint:(NSNumber *)endpoint { - MTR_LOG_DEBUG("%@ _setDSTOffset with offset: %d, validStarting: %llu, validUntil: %llu, endpoint %@", - self, - dstOffset, validStarting, validUntil, endpoint); + MTR_LOG_DEBUG("%@ _setDSTOffsets with offsets: %@, endpoint %@", + self, dstOffsets, endpoint); MTRTimeSynchronizationClusterSetDSTOffsetParams * params = [[MTRTimeSynchronizationClusterSetDSTOffsetParams alloc] init]; - MTRTimeSynchronizationClusterDSTOffsetStruct * dstOffsetStruct = [[MTRTimeSynchronizationClusterDSTOffsetStruct alloc] init]; - dstOffsetStruct.offset = @(dstOffset); - dstOffsetStruct.validStarting = @(validStarting); - dstOffsetStruct.validUntil = @(validUntil); - params.dstOffset = @[ dstOffsetStruct ]; + params.dstOffset = dstOffsets; auto setDSTOffsetResponseHandler = ^(id _Nullable response, NSError * _Nullable error) { if (error) { - MTR_LOG_ERROR("%@ _setDSTOffset failed on endpoint %@, with parameters %@, error: %@", self, endpoint, params, error); + MTR_LOG_ERROR("%@ _setDSTOffsets failed on endpoint %@, with parameters %@, error: %@", self, endpoint, params, error); } }; diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index fa90eb2f542831..d40e269a753d96 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -44,6 +44,7 @@ #import "MTRPersistentStorageDelegateBridge.h" #import "MTRServerEndpoint_Internal.h" #import "MTRSetupPayload.h" +#import "MTRTimeUtils.h" #import "NSDataSpanConversion.h" #import "NSStringSpanConversion.h" #import @@ -737,40 +738,34 @@ - (BOOL)commissionNodeWithID:(NSNumber *)nodeID // to add, but in practice devices likely support only 2 and // AutoCommissioner caps the list at 10. Let's do up to 4 transitions // for now. + constexpr size_t dstOffsetMaxCount = 4; using DSTOffsetType = chip::app::Clusters::TimeSynchronization::Structs::DSTOffsetStruct::Type; - - DSTOffsetType dstOffsets[4]; - size_t dstOffsetCount = 0; - auto nextOffset = tz.daylightSavingTimeOffset; - uint64_t nextValidStarting = 0; - auto * nextTransition = tz.nextDaylightSavingTimeTransition; - for (auto & dstOffset : dstOffsets) { - ++dstOffsetCount; - dstOffset.offset = static_cast(nextOffset); - dstOffset.validStarting = nextValidStarting; - if (nextTransition != nil) { - uint32_t transitionEpochS; - if (DateToMatterEpochSeconds(nextTransition, transitionEpochS)) { - using Microseconds64 = chip::System::Clock::Microseconds64; - using Seconds32 = chip::System::Clock::Seconds32; - dstOffset.validUntil.SetNonNull(Microseconds64(Seconds32(transitionEpochS)).count()); + // dstOffsets needs to live long enough, so its existence is not + // conditional on having offsets. + DSTOffsetType dstOffsets[dstOffsetMaxCount]; + + auto * offsets = MTRComputeDSTOffsets(dstOffsetMaxCount); + if (offsets != nil) { + size_t dstOffsetCount = 0; + for (MTRTimeSynchronizationClusterDSTOffsetStruct * offset in offsets) { + if (dstOffsetCount >= dstOffsetMaxCount) { + // Really shouldn't happen, but let's be extra careful about + // buffer overruns. + break; + } + auto & targetOffset = dstOffsets[dstOffsetCount]; + targetOffset.offset = offset.offset.intValue; + targetOffset.validStarting = offset.validStarting.unsignedLongLongValue; + if (offset.validUntil == nil) { + targetOffset.validUntil.SetNull(); } else { - // Out of range; treat as "forever". - dstOffset.validUntil.SetNull(); + targetOffset.validUntil.SetNonNull(offset.validUntil.unsignedLongLongValue); } - } else { - dstOffset.validUntil.SetNull(); - } - - if (dstOffset.validUntil.IsNull()) { - break; + ++dstOffsetCount; } - nextOffset = [tz daylightSavingTimeOffsetForDate:nextTransition]; - nextValidStarting = dstOffset.validUntil.Value(); - nextTransition = [tz nextDaylightSavingTimeTransitionAfterDate:nextTransition]; + params.SetDSTOffsets(chip::app::DataModel::List(dstOffsets, dstOffsetCount)); } - params.SetDSTOffsets(chip::app::DataModel::List(dstOffsets, dstOffsetCount)); chip::NodeId deviceId = [nodeID unsignedLongLongValue]; self->_operationalCredentialsDelegate->SetDeviceID(deviceId); diff --git a/src/darwin/Framework/CHIP/MTRTimeUtils.h b/src/darwin/Framework/CHIP/MTRTimeUtils.h new file mode 100644 index 00000000000000..6295619535f11f --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRTimeUtils.h @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import +#import +#import + +#import "MTRDefines_Internal.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Utility method to compute up to the given count of instances of + * MTRTimeSynchronizationClusterDSTOffsetStruct and return them. + * + * Returns nil on errors. + */ +MTR_EXTERN MTR_TESTABLE + NSArray * _Nullable MTRComputeDSTOffsets(size_t maxCount); + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRTimeUtils.mm b/src/darwin/Framework/CHIP/MTRTimeUtils.mm new file mode 100644 index 00000000000000..87bc39b736f4cd --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRTimeUtils.mm @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +#import "MTRTimeUtils.h" +#import "MTRConversion.h" +#import "MTRLogging_Internal.h" + +NSArray * _Nullable MTRComputeDSTOffsets(size_t maxCount) +{ + auto * tz = [NSTimeZone localTimeZone]; + if (!tz) { + MTR_LOG_ERROR("Could not retrieve local time zone. Unable to determine DST offsets."); + return nil; + } + + NSMutableArray * retval = [NSMutableArray arrayWithCapacity:maxCount]; + + auto nextOffset = tz.daylightSavingTimeOffset; + NSNumber * nextValidStarting = @(0); + auto * nextTransition = tz.nextDaylightSavingTimeTransition; + for (size_t offsetsAdded = 0; offsetsAdded < maxCount; ++offsetsAdded) { + auto offset = [[MTRTimeSynchronizationClusterDSTOffsetStruct alloc] init]; + offset.offset = @(nextOffset); + offset.validStarting = nextValidStarting; + if (nextTransition == nil) { + // This one is valid forever. + offset.validUntil = nil; + } else { + uint64_t nextTransitionEpochUs; + if (!DateToMatterEpochMicroseconds(nextTransition, nextTransitionEpochUs)) { + // Transition is somehow before Matter epoch start. This really + // should not happen, but if it does just don't pretend like we + // know what's going on with timezones here. + MTR_LOG_ERROR("Future daylight savings transition is before Matter epoch start?"); + return nil; + } + + offset.validUntil = @(nextTransitionEpochUs); + } + + [retval addObject:offset]; + + if (offset.validUntil == nil) { + // Valid forever, so no need for more offsets. + break; + } + + nextOffset = [tz daylightSavingTimeOffsetForDate:nextTransition]; + nextValidStarting = offset.validUntil; + nextTransition = [tz nextDaylightSavingTimeTransitionAfterDate:nextTransition]; + } + + return [retval copy]; +} diff --git a/src/darwin/Framework/CHIPTests/MTRDSTOffsetTests.m b/src/darwin/Framework/CHIPTests/MTRDSTOffsetTests.m new file mode 100644 index 00000000000000..18c8866380b9bd --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MTRDSTOffsetTests.m @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ + +// module headers +#import "MTRTimeUtils.h" +#import + +// system dependencies +#import + +@interface MTRDSTOffsetTests : XCTestCase +@end + +@implementation MTRDSTOffsetTests + +- (void)test001_SingleOffset +{ + __auto_type * offsets = MTRComputeDSTOffsets(1); + // We should be able to get offsets. + XCTAssertNotNil(offsets); + + // And there is always at least one, even if all it says is "no offset, forever". + XCTAssertEqual(offsets.count, 1); + XCTAssertEqualObjects(offsets[0].validStarting, @(0)); +} + +- (void)test002_TryGetting2Offsets +{ + __auto_type * offsets = MTRComputeDSTOffsets(2); + // We should be able to get offsets. + XCTAssertNotNil(offsets); + + // And there is always at least one, even if all it says is "no offset, + // forever". And we should not get too many offsets. + XCTAssertTrue(offsets.count >= 1); + XCTAssertTrue(offsets.count <= 2); + NSNumber * previousValidUntil = @(0); + for (MTRTimeSynchronizationClusterDSTOffsetStruct * offset in offsets) { + XCTAssertEqualObjects(previousValidUntil, offset.validStarting); + previousValidUntil = offset.validUntil; + } +} + +@end diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 606d0f144544a9..305be3271b0372 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -200,6 +200,8 @@ 51B22C222740CB1D008D5055 /* MTRCommandPayloadsObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B22C212740CB1D008D5055 /* MTRCommandPayloadsObjc.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51B22C262740CB32008D5055 /* MTRStructsObjc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51B22C252740CB32008D5055 /* MTRStructsObjc.mm */; }; 51B22C2A2740CB47008D5055 /* MTRCommandPayloadsObjc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51B22C292740CB47008D5055 /* MTRCommandPayloadsObjc.mm */; }; + 51C659D92BA3787500C54922 /* MTRTimeUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 51C659D72BA3787500C54922 /* MTRTimeUtils.h */; }; + 51C659DA2BA3787500C54922 /* MTRTimeUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51C659D82BA3787500C54922 /* MTRTimeUtils.mm */; }; 51C8E3F82825CDB600D47D00 /* MTRTestKeys.m in Sources */ = {isa = PBXBuildFile; fileRef = 51C8E3F72825CDB600D47D00 /* MTRTestKeys.m */; }; 51C984622A61CE2A00B0AD9A /* MTRFabricInfoChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 51C984602A61CE2A00B0AD9A /* MTRFabricInfoChecker.m */; }; 51D0B1272B617246006E3511 /* MTRServerEndpoint.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B1252B617246006E3511 /* MTRServerEndpoint.mm */; }; @@ -214,6 +216,7 @@ 51D0B1402B61B3A4006E3511 /* MTRServerCluster.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D0B13E2B61B3A3006E3511 /* MTRServerCluster.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51D0B1412B61B3A4006E3511 /* MTRServerCluster.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51D0B13F2B61B3A3006E3511 /* MTRServerCluster.mm */; }; 51D10D2E2808E2CA00E8CA3D /* MTRTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 51D10D2D2808E2CA00E8CA3D /* MTRTestStorage.m */; }; + 51D9CB0B2BA37DCE0049D6DB /* MTRDSTOffsetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 51D9CB0A2BA37DCE0049D6DB /* MTRDSTOffsetTests.m */; }; 51E0FC102ACBBF230001E197 /* MTRSwiftDeviceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E0FC0F2ACBBF230001E197 /* MTRSwiftDeviceTests.swift */; }; 51E24E73274E0DAC007CCF6E /* MTRErrorTestUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51E24E72274E0DAC007CCF6E /* MTRErrorTestUtils.mm */; }; 51E51FBF282AD37A00FC978D /* MTRDeviceControllerStartupParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E51FBC282AD37A00FC978D /* MTRDeviceControllerStartupParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -600,6 +603,8 @@ 51B22C252740CB32008D5055 /* MTRStructsObjc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRStructsObjc.mm; sourceTree = ""; }; 51B22C292740CB47008D5055 /* MTRCommandPayloadsObjc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRCommandPayloadsObjc.mm; sourceTree = ""; }; 51B6C5C72AD85B47003F4D3A /* MTRCommandPayloads_Internal.zapt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MTRCommandPayloads_Internal.zapt; sourceTree = ""; }; + 51C659D72BA3787500C54922 /* MTRTimeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRTimeUtils.h; sourceTree = ""; }; + 51C659D82BA3787500C54922 /* MTRTimeUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRTimeUtils.mm; sourceTree = ""; }; 51C8E3F72825CDB600D47D00 /* MTRTestKeys.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestKeys.m; sourceTree = ""; }; 51C984602A61CE2A00B0AD9A /* MTRFabricInfoChecker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRFabricInfoChecker.m; sourceTree = ""; }; 51C984612A61CE2A00B0AD9A /* MTRFabricInfoChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRFabricInfoChecker.h; sourceTree = ""; }; @@ -615,6 +620,7 @@ 51D0B13E2B61B3A3006E3511 /* MTRServerCluster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRServerCluster.h; sourceTree = ""; }; 51D0B13F2B61B3A3006E3511 /* MTRServerCluster.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRServerCluster.mm; sourceTree = ""; }; 51D10D2D2808E2CA00E8CA3D /* MTRTestStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRTestStorage.m; sourceTree = ""; }; + 51D9CB0A2BA37DCE0049D6DB /* MTRDSTOffsetTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTRDSTOffsetTests.m; sourceTree = ""; }; 51E0FC0F2ACBBF230001E197 /* MTRSwiftDeviceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MTRSwiftDeviceTests.swift; sourceTree = ""; }; 51E24E72274E0DAC007CCF6E /* MTRErrorTestUtils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRErrorTestUtils.mm; sourceTree = ""; }; 51E51FBC282AD37A00FC978D /* MTRDeviceControllerStartupParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceControllerStartupParams.h; sourceTree = ""; }; @@ -1320,6 +1326,8 @@ 2C8C8FBE253E0C2100797F05 /* MTRStorage.h */, 997DED172695344800975E97 /* MTRThreadOperationalDataset.h */, 997DED152695343400975E97 /* MTRThreadOperationalDataset.mm */, + 51C659D72BA3787500C54922 /* MTRTimeUtils.h */, + 51C659D82BA3787500C54922 /* MTRTimeUtils.mm */, 3D843710294977000070D20A /* NSDataSpanConversion.h */, 3D84370E294977000070D20A /* NSStringSpanConversion.h */, 5117DD3729A931AE00FFA1AA /* MTROperationalBrowser.h */, @@ -1342,6 +1350,7 @@ 3DFCB3282966684500332B35 /* MTRCertificateInfoTests.m */, 99C65E0F267282F1003402F6 /* MTRControllerTests.m */, 5AE6D4E327A99041001F2493 /* MTRDeviceTests.m */, + 51D9CB0A2BA37DCE0049D6DB /* MTRDSTOffsetTests.m */, 3D0C484A29DA4FA0006D811F /* MTRErrorTests.m */, 5A6FEC9C27B5E48800F25F42 /* MTRXPCProtocolTests.m */, 5A7947DD27BEC3F500434CF2 /* MTRXPCListenerSampleTests.m */, @@ -1589,6 +1598,7 @@ 1EC4CE6425CC276600D7304F /* MTRBaseClusters.h in Headers */, 3D843712294977000070D20A /* MTRCallbackBridgeBase.h in Headers */, 3DECCB742934C21B00585AEC /* MTRDefines.h in Headers */, + 51C659D92BA3787500C54922 /* MTRTimeUtils.h in Headers */, 75139A702B7FE68C00E3A919 /* MTRDeviceControllerLocalTestStorage.h in Headers */, 2C5EEEF6268A85C400CAE3D3 /* MTRDeviceConnectionBridge.h in Headers */, 2C8C8FC0253E0C2100797F05 /* MTRPersistentStorageDelegateBridge.h in Headers */, @@ -1928,6 +1938,7 @@ 3DA1A3562ABAB3B4004F0BB9 /* MTRAsyncWorkQueue.mm in Sources */, 51D0B1272B617246006E3511 /* MTRServerEndpoint.mm in Sources */, 3DECCB722934AFE200585AEC /* MTRLogging.mm in Sources */, + 51C659DA2BA3787500C54922 /* MTRTimeUtils.mm in Sources */, 7596A84528762729004DAE0E /* MTRDevice.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1970,6 +1981,7 @@ 519498322A25581C00B3BABE /* MTRSetupPayloadSerializerTests.m in Sources */, 51A2F1322A00402A00F03298 /* MTRDataValueParserTests.m in Sources */, 51E95DF82A78110900A434F0 /* MTRPerControllerStorageTests.m in Sources */, + 51D9CB0B2BA37DCE0049D6DB /* MTRDSTOffsetTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From d166650657bd49a84fbd4efe770199f59033a310 Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Fri, 15 Mar 2024 10:06:15 -0700 Subject: [PATCH 17/27] Disable QEMU ESP32 test until it's fixed (#32588) --- .github/workflows/qemu.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index c4992953780911..714b5b8782f1ae 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -58,6 +58,8 @@ jobs: build \ " - name: Run all tests + # Disabled being tracked here: https://github.com/project-chip/connectedhomeip/issues/32587 + if: false run: | src/test_driver/esp32/run_qemu_image.py \ --verbose \ From 3b1c825efab7825a6a29192537caa187a1bb02ca Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 15 Mar 2024 18:25:55 +0100 Subject: [PATCH 18/27] [Python] Add ruff as Python linter (#32384) * Add ruff as Python linter * Add ruff to linter workflow * Remove flake8 * Set default line-length and explicitly select rules * Address REPL Test issue * Explicitly set line lenght for autopep8 * Fix restyled config --- .flake8 | 6 ------ .github/workflows/lint.yml | 9 ++++----- .restyled.yaml | 2 ++ .../docker/images/base/chip-build/Dockerfile | 1 - ruff.toml | 19 +++++++++++++++++++ .../matter_idl/generators/java/__init__.py | 1 - .../matter_idl/generators/kotlin/__init__.py | 1 - .../matter_yamltests/parser.py | 4 ++-- .../wildcard_response_extractor_cluster.py | 2 +- .../generate_nrfconnect_chip_factory_data.py | 2 +- scripts/tools/silabs/FactoryDataProvider.py | 2 +- src/python_testing/TestSpecParsingSupport.py | 2 +- src/python_testing/spec_parsing_support.py | 8 ++++---- .../linux-cirque/helper/CHIPTestBase.py | 4 ++-- 14 files changed, 37 insertions(+), 26 deletions(-) delete mode 100644 .flake8 create mode 100644 ruff.toml diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 65b1c2a93da0e8..00000000000000 --- a/.flake8 +++ /dev/null @@ -1,6 +0,0 @@ -[flake8] -max-line-length = 132 -exclude = third_party - .* - out/* - ./examples/common/QRCode/* diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 70edd539eee0bf..8e67de5878ccf9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:39 steps: - name: Checkout @@ -267,12 +267,11 @@ jobs: run: | git grep -I -n 'emberAfWriteAttribute' -- './*' ':(exclude).github/workflows/lint.yml' ':(exclude)src/app/util/af.h' ':(exclude)zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp' ':(exclude)src/app/zap-templates/templates/app/attributes/Accessors-src.zapt' ':(exclude)src/app/util/attribute-table.cpp' ':(exclude)examples/common/pigweed/rpc_services/Attributes.h' ':(exclude)src/app/util/attribute-table.h' ':(exclude)src/app/util/ember-compatibility-functions.cpp' && exit 1 || exit 0 - # Run python Linter (flake8) and verify python files - # ignore some style errors, restyler should do that - - name: Check for errors using flake8 Python linter + # Run ruff python linter + - name: Check for errors using ruff Python linter if: always() run: | - flake8 --extend-ignore=E501,W391 + ruff check # git grep exits with 0 if it finds a match, but we want # to fail (exit nonzero) on match. And we want to exclude this file, diff --git a/.restyled.yaml b/.restyled.yaml index 56ed3061e6e1fa..9acbe8e5134aed 100644 --- a/.restyled.yaml +++ b/.restyled.yaml @@ -230,6 +230,8 @@ restylers: command: - autopep8 - "--in-place" + - "--max-line-length" + - "132" arguments: [] include: - "**/*.py" diff --git a/integrations/docker/images/base/chip-build/Dockerfile b/integrations/docker/images/base/chip-build/Dockerfile index 39aada6fec7af8..a2f83fc275a4b0 100644 --- a/integrations/docker/images/base/chip-build/Dockerfile +++ b/integrations/docker/images/base/chip-build/Dockerfile @@ -144,7 +144,6 @@ RUN set -x \ click \ coloredlogs \ cxxfilt \ - flake8 \ future \ ghapi \ mobly \ diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000000000..fec7608d707987 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,19 @@ +exclude = [ + ".environment", + ".git", + ".github", + ".*", + "build", + "out", + "third_party", + "examples/common/QRCode/", +] +target-version = "py37" + +line-length = 132 + +[lint] +select = ["E4", "E7", "E9", "F"] +ignore = [ + "E721" # We use is for good reasons +] diff --git a/scripts/py_matter_idl/matter_idl/generators/java/__init__.py b/scripts/py_matter_idl/matter_idl/generators/java/__init__.py index da7eef254f9fa7..96fa37c1a26445 100644 --- a/scripts/py_matter_idl/matter_idl/generators/java/__init__.py +++ b/scripts/py_matter_idl/matter_idl/generators/java/__init__.py @@ -114,7 +114,6 @@ def FieldToGlobalName(field: Field, context: TypeLookupContext) -> Optional[str] 'event_no': 'chip::EventNumber', 'fabric_id': 'chip::FabricId', 'fabric_idx': 'chip::FabricIndex', - 'fabric_idx': 'chip::FabricIndex', 'field_id': 'chip::FieldId', 'group_id': 'chip::GroupId', 'node_id': 'chip::NodeId', diff --git a/scripts/py_matter_idl/matter_idl/generators/kotlin/__init__.py b/scripts/py_matter_idl/matter_idl/generators/kotlin/__init__.py index b069319d7f586c..17e5136e169b6a 100644 --- a/scripts/py_matter_idl/matter_idl/generators/kotlin/__init__.py +++ b/scripts/py_matter_idl/matter_idl/generators/kotlin/__init__.py @@ -114,7 +114,6 @@ def FieldToGlobalName(field: Field, context: TypeLookupContext) -> Optional[str] 'event_no': 'chip::EventNumber', 'fabric_id': 'chip::FabricId', 'fabric_idx': 'chip::FabricIndex', - 'fabric_idx': 'chip::FabricIndex', 'field_id': 'chip::FieldId', 'group_id': 'chip::GroupId', 'node_id': 'chip::NodeId', diff --git a/scripts/py_matter_yamltests/matter_yamltests/parser.py b/scripts/py_matter_yamltests/matter_yamltests/parser.py index cb0a89c6418756..9612d5749d085b 100644 --- a/scripts/py_matter_yamltests/matter_yamltests/parser.py +++ b/scripts/py_matter_yamltests/matter_yamltests/parser.py @@ -1086,7 +1086,7 @@ def _response_values_validation(self, expected_response, received_response, resu break received_value = received_response.get('value') - if not self.is_attribute and not self.is_event and not (self.command in ANY_COMMANDS_LIST): + if not self.is_attribute and not self.is_event and self.command not in ANY_COMMANDS_LIST: expected_name = value.get('name') if expected_name not in received_value: result.error(check_type, error_name_does_not_exist.format( @@ -1173,7 +1173,7 @@ def _maybe_save_as(self, key: str, default_target: str, expected_response, recei continue received_value = received_response.get(default_target) - if not self.is_attribute and not self.is_event and not (self.command in ANY_COMMANDS_LIST): + if not self.is_attribute and not self.is_event and self.command not in ANY_COMMANDS_LIST: expected_name = value.get('name') if received_value is None or expected_name not in received_value: result.error(check_type, error_name_does_not_exist.format( diff --git a/scripts/tests/yaml/extensions/wildcard_response_extractor_cluster.py b/scripts/tests/yaml/extensions/wildcard_response_extractor_cluster.py index a0e34cc148d8ac..2f60a526bb6b16 100644 --- a/scripts/tests/yaml/extensions/wildcard_response_extractor_cluster.py +++ b/scripts/tests/yaml/extensions/wildcard_response_extractor_cluster.py @@ -113,7 +113,7 @@ def __get_argument(self, request, argument_name): if arguments is None: return None - if not type(arguments) is list: + if type(arguments) is not list: return None for argument in arguments: diff --git a/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py b/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py index 7cddb766a90cca..724484058029d1 100644 --- a/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py +++ b/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py @@ -246,7 +246,7 @@ def _validate_args(self): "Cannot find paths to DAC or PAI certificates .der files. To generate a new ones please provide a path to chip-cert executable (--chip_cert_path) and add --gen_certs argument" assert self._args.output.endswith(".json"), \ "Output path doesn't contain .json file path. ({})".format(self._args.output) - assert not (self._args.passcode in INVALID_PASSCODES), \ + assert self._args.passcode not in INVALID_PASSCODES, \ "Provided invalid passcode!" def generate_json(self): diff --git a/scripts/tools/silabs/FactoryDataProvider.py b/scripts/tools/silabs/FactoryDataProvider.py index 8cb489587cb122..3ff8ece3bf735f 100644 --- a/scripts/tools/silabs/FactoryDataProvider.py +++ b/scripts/tools/silabs/FactoryDataProvider.py @@ -140,7 +140,7 @@ def __init__(self, arguments) -> None: assert (bool(arguments.gen_spake2p_path) != bool(arguments.spake2_verifier) ), "Provide either the spake2_verifier string or the path to the spake2 generator" - assert not (arguments.passcode in INVALID_PASSCODES), "The provided passcode is invalid" + assert arguments.passcode not in INVALID_PASSCODES, "The provided passcode is invalid" self._args = arguments diff --git a/src/python_testing/TestSpecParsingSupport.py b/src/python_testing/TestSpecParsingSupport.py index 2ff80e55244648..2fdfcdcd40ef1f 100644 --- a/src/python_testing/TestSpecParsingSupport.py +++ b/src/python_testing/TestSpecParsingSupport.py @@ -262,7 +262,7 @@ def test_derived_clusters(self): 0, "Unexpected number of unknown commands in base") asserts.assert_equal(len(clusters[0xFFFF].unknown_commands), 2, "Unexpected number of unknown commands in derived cluster") - combine_derived_clusters_with_base(clusters, pure_base_clusters, ids_by_name) + combine_derived_clusters_with_base(clusters, pure_base_clusters, ids_by_name, problems) # Ensure the base-only attribute (1) was added to the derived cluster asserts.assert_equal(set(clusters[0xFFFF].attributes.keys()), set( [0, 1, 2, 3] + expected_global_attrs), "Unexpected attribute list") diff --git a/src/python_testing/spec_parsing_support.py b/src/python_testing/spec_parsing_support.py index 6a9d1f87ae3442..ac344638c0cdd1 100644 --- a/src/python_testing/spec_parsing_support.py +++ b/src/python_testing/spec_parsing_support.py @@ -487,7 +487,7 @@ def remove_problem(location: typing.Union[CommandPathLocation, FeaturePathLocati clusters[action_id].accepted_commands[c].conformance = optional() remove_problem(CommandPathLocation(endpoint_id=0, cluster_id=action_id, command_id=c)) - combine_derived_clusters_with_base(clusters, pure_base_clusters, ids_by_name) + combine_derived_clusters_with_base(clusters, pure_base_clusters, ids_by_name, problems) for alias_base_name, aliased_clusters in CLUSTER_ALIASES.items(): for id, (alias_name, pics) in aliased_clusters.items(): @@ -547,10 +547,10 @@ def remove_problem(location: typing.Union[CommandPathLocation, FeaturePathLocati return clusters, problems -def combine_derived_clusters_with_base(xml_clusters: dict[int, XmlCluster], pure_base_clusters: dict[str, XmlCluster], ids_by_name: dict[str, int]) -> None: +def combine_derived_clusters_with_base(xml_clusters: dict[int, XmlCluster], pure_base_clusters: dict[str, XmlCluster], ids_by_name: dict[str, int], problems: list[ProblemNotice]) -> None: ''' Overrides base elements with the derived cluster values for derived clusters. ''' - def combine_attributes(base: dict[uint, XmlAttribute], derived: dict[uint, XmlAttribute], cluster_id: uint) -> dict[uint, XmlAttribute]: + def combine_attributes(base: dict[uint, XmlAttribute], derived: dict[uint, XmlAttribute], cluster_id: uint, problems: list[ProblemNotice]) -> dict[uint, XmlAttribute]: ret = deepcopy(base) extras = {k: v for k, v in derived.items() if k not in base.keys()} overrides = {k: v for k, v in derived.items() if k in base.keys()} @@ -590,7 +590,7 @@ def combine_attributes(base: dict[uint, XmlAttribute], derived: dict[uint, XmlAt command_map.update(c.command_map) features = deepcopy(base.features) features.update(c.features) - attributes = combine_attributes(base.attributes, c.attributes, id) + attributes = combine_attributes(base.attributes, c.attributes, id, problems) accepted_commands = deepcopy(base.accepted_commands) accepted_commands.update(c.accepted_commands) generated_commands = deepcopy(base.generated_commands) diff --git a/src/test_driver/linux-cirque/helper/CHIPTestBase.py b/src/test_driver/linux-cirque/helper/CHIPTestBase.py index 036ba25a8538ff..7fd0c81e86f3ca 100644 --- a/src/test_driver/linux-cirque/helper/CHIPTestBase.py +++ b/src/test_driver/linux-cirque/helper/CHIPTestBase.py @@ -299,13 +299,13 @@ def assertTrue(self, exp, note=None): assert(Not)Equal python unittest style functions that raise exceptions when condition not met ''' - if not (exp is True): + if exp is not True: if note: self.logger.error(note) raise AssertionError def assertFalse(self, exp, note=None): - if not (exp is False): + if exp is not False: if note: self.logger.error(note) raise AssertionError From 1d6b13e54efe5a344c90b8ada8824f3e1178929b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Fri, 15 Mar 2024 18:27:06 +0100 Subject: [PATCH 19/27] Update RelativeHumidityMeasurement in Matter Linux Air Quality Example README.md (#32254) * Update Matter Linux Air Quality Example README.md * restyled * Update README.md * Update README.md * restyled --- examples/air-quality-sensor-app/linux/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/air-quality-sensor-app/linux/README.md b/examples/air-quality-sensor-app/linux/README.md index 651afa4c6297ee..b1c2e92461d8f0 100644 --- a/examples/air-quality-sensor-app/linux/README.md +++ b/examples/air-quality-sensor-app/linux/README.md @@ -136,10 +136,11 @@ $ echo '{"Name":"TemperatureMeasurement","NewValue":1800}' > /tmp/chip_air_quali ### Trigger Humidity change event -Generate event `RelativeHumidityMeasurement`, to change the temperate value. +Generate event `RelativeHumidityMeasurement`, to change the relative humidity +value (6000 for 60,0 %). ``` -$ echo '{"Name":"RelativeHumidityMeasurement","NewValue":60}' > /tmp/chip_air_quality_fifo_ +$ echo '{"Name":"RelativeHumidityMeasurement","NewValue":6000}' > /tmp/chip_air_quality_fifo_ ``` ### Trigger concentration change event From a0b2fe2d0b2e291d82238d37f678ca207716f080 Mon Sep 17 00:00:00 2001 From: Sharad Binjola <31142146+sharadb-amazon@users.noreply.github.com> Date: Fri, 15 Mar 2024 12:13:31 -0700 Subject: [PATCH 20/27] Android tv-casting-app: simplified Endpoint API (#32552) --- examples/tv-casting-app/APIs.md | 142 +++++-- .../com/chip/casting/app/MainActivity.java | 20 +- .../util/PreferencesConfigurationManager.java | 153 -------- .../casting/ActionSelectorFragment.java | 97 +++++ .../casting/ConnectionExampleFragment.java | 101 ++--- ...ntentLauncherLaunchURLExampleFragment.java | 105 ++++++ .../casting/DiscoveryExampleFragment.java | 13 +- .../matter/casting/InitializationExample.java | 3 +- .../PreferencesConfigurationManager.java | 265 ++++++++++++++ .../com/matter/casting/core/CastingApp.java | 17 +- .../matter/casting/core/CastingAppState.java | 16 + .../matter/casting/core/CastingPlayer.java | 38 +- .../jni/com/matter/casting/core/Endpoint.java | 33 ++ .../casting/core/MatterCastingPlayer.java | 24 +- .../matter/casting/core/MatterEndpoint.java | 59 +++ .../casting/support/EndpointFilter.java | 2 + .../casting/support/MatterCallback.java | 34 ++ .../src/main/jni/cpp/core/CastingApp-JNI.cpp | 36 +- .../main/jni/cpp/core/CastingPlayer-JNI.cpp | 171 --------- .../cpp/core/CastingPlayerDiscovery-JNI.cpp | 39 +- .../jni/cpp/core/MatterCastingPlayer-JNI.cpp | 141 +++++++ .../jni/cpp/core/MatterCastingPlayer-JNI.h | 48 +++ .../main/jni/cpp/core/MatterEndpoint-JNI.cpp | 91 +++++ ...stingPlayer-JNI.h => MatterEndpoint-JNI.h} | 17 +- .../support/CastingPlayerConverter-JNI.cpp | 96 ----- .../cpp/support/CastingPlayerConverter-JNI.h | 41 --- .../main/jni/cpp/support/Converters-JNI.cpp | 346 ++++++++++++++++++ .../src/main/jni/cpp/support/Converters-JNI.h | 77 ++++ .../jni/cpp/support/ErrorConverter-JNI.cpp | 43 --- .../main/jni/cpp/support/ErrorConverter-JNI.h | 31 -- .../main/jni/cpp/support/MatterCallback-JNI.h | 95 +++++ .../RotatingDeviceIdUniqueIdProvider-JNI.cpp | 2 +- .../fragment_matter_action_selector.xml | 39 ++ ...ent_matter_content_launcher_launch_url.xml | 88 +++++ .../App/app/src/main/res/values/strings.xml | 3 + examples/tv-casting-app/android/BUILD.gn | 15 +- examples/tv-casting-app/android/args.gni | 2 + .../tv-casting-common/core/CastingPlayer.cpp | 10 +- .../support/CastingStore.cpp | 1 + .../support/EndpointListLoader.cpp | 24 +- 40 files changed, 1881 insertions(+), 697 deletions(-) delete mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/PreferencesConfigurationManager.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/PreferencesConfigurationManager.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/MatterCallback.java delete mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp rename examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/{CastingPlayer-JNI.h => MatterEndpoint-JNI.h} (66%) delete mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp delete mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.h create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h delete mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.cpp delete mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.h create mode 100644 examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h create mode 100644 examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml create mode 100644 examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_content_launcher_launch_url.xml diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index f254df1d7e2d14..d4a51dcc3146bf 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -34,15 +34,11 @@ samples so you can see the experience end to end. A Casting Client (e.g. a mobile phone app) is expected to be a Matter Commissionable Node and a `CastingPlayer` (i.e. a TV) is expected to be a Matter -Commissioner. In the context of the -[Matter Video Player architecture](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc), -a `CastingPlayer` would map to -[Casting "Video" Player](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc#1-introduction). -The `CastingPlayer` is expected to be hosting one or more `Endpoints` (some of -which can represent -[Content Apps](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc#1-introduction) -in the Matter Video Player architecture) that support one or more Matter Media -`Clusters`. +Commissioner. In the context of the Matter Video Player architecture, a +`CastingPlayer` would map to Casting "Video" Player. The `CastingPlayer` is +expected to be hosting one or more `Endpoints` (some of which can represent +Content Apps in the Matter Video Player architecture) that support one or more +Matter Media `Clusters`. The steps to start a casting session are: @@ -80,6 +76,8 @@ consume each platform's specific libraries. The libraries MUST be built with the client's specific values for `CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID` and `CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID` updated in the [CHIPProjectAppConfig.h](tv-casting-common/include/CHIPProjectAppConfig.h) file. +Other values like the `CHIP_DEVICE_CONFIG_DEVICE_NAME` may be updated as well to +correspond to the client being built. ### Initialize the Casting Client @@ -91,10 +89,10 @@ A Casting Client must first initialize the Matter SDK and define the following `DataProvider` objects for the the Matter Casting library to use throughout the client's lifecycle: -1. **Rotating Device Identifier** - Refer to the Matter specification for - details on how to generate the - [Rotating Device Identifier](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/rendezvous/DeviceDiscovery.adoc#245-rotating-device-identifier)). - Then, instantiate a `DataProvider` object as described below. +1. **Rotating Device Identifier** - "This unique per-device identifier SHALL + consist of a randomly-generated 128-bit or longer octet string." Refer to + the Matter specification for more details. Instantiate a `DataProvider` + object as described below to provide this identifier. On Linux, define a `RotatingDeviceIdUniqueIdProvider` to provide the Casting Client's `RotatingDeviceIdUniqueId`, by implementing a @@ -152,10 +150,13 @@ client's lifecycle: ``` 2. **Commissioning Data** - This object contains the passcode, discriminator, - etc which identify the app and are provided to the `CastingPlayer` during - the commissioning process. Refer to the Matter specification's - [Onboarding Payload](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/qr_code/OnboardingPayload.adoc#ref_OnboardingPayload) - section for details on commissioning data. + etc. which identify the app and are provided to the `CastingPlayer` during + the commissioning process. "A Passcode SHALL be included as a 27-bit + unsigned integer, which serves as proof of possession during commissioning." + "A Discriminator SHALL be included as a 12-bit unsigned integer, which SHALL + match the value which a device advertises during commissioning." Refer to + the Matter specification's "Onboarding Payload" section for more details on + commissioning data. On Linux, define a function `InitCommissionableDataProvider` to initialize initialize a `LinuxCommissionableDataProvider` that can provide the required @@ -217,9 +218,8 @@ client's lifecycle: 3. **Device Attestation Credentials** - This object contains the `DeviceAttestationCertificate`, `ProductAttestationIntermediateCertificate`, etc. and implements a way to sign messages when called upon by the Matter TV - Casting Library as part of the - [Device Attestation process](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/device_attestation/Device_Attestation_Specification.adoc) - during commissioning. + Casting Library as part of the Matter Device Attestation process during + commissioning. On Linux, implement a define a `dacProvider` to provide the Casting Client's Device Attestation Credentials, by implementing a @@ -487,8 +487,8 @@ potentially skipping the longer commissioning process and instead, simply re-establishing the CASE session. This cache can be cleared by calling the `ClearCache` API on the `CastingApp`, say when the user signs out of the app. See API and its documentation for [Linux](tv-casting-common/core/CastingApp.h), -Android and -[iOS](darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp.h). +[Android](android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java) +and [iOS](darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingApp.h). ### Discover Casting Players @@ -702,10 +702,9 @@ Each `CastingPlayer` object created during [Discovery](#discover-casting-players) contains information such as `deviceName`, `vendorId`, `productId`, etc. which can help the user pick the right `CastingPlayer`. A Casting Client can attempt to connect to the -`selectedCastingPlayer` using -[Matter User Directed Commissioning (UDC)](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/rendezvous/UserDirectedCommissioning.adoc). -The Matter TV Casting library locally caches information required to reconnect -to a `CastingPlayer`, once the Casting client has been commissioned by it. After +`selectedCastingPlayer` using Matter User Directed Commissioning (UDC). The +Matter TV Casting library locally caches information required to reconnect to a +`CastingPlayer`, once the Casting client has been commissioned by it. After that, the Casting client is able to skip the full UDC process by establishing CASE with the `CastingPlayer` directly. Once connected, the `CastingPlayer` object will contain the list of available Endpoints on that `CastingPlayer`. @@ -743,6 +742,60 @@ targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler, ... ``` +On Android, the Casting Client may call `verifyOrEstablishConnection` on the +`CastingPlayer` object it wants to connect to. + +```java +private static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; + +EndpointFilter desiredEndpointFilter = new EndpointFilter(); +desiredEndpointFilter.vendorId = DESIRED_ENDPOINT_VENDOR_ID; + +MatterError err = targetCastingPlayer.verifyOrEstablishConnection( + MIN_CONNECTION_TIMEOUT_SEC, + desiredEndpointFilter, + new MatterCallback() { + @Override + public void handle(Void v) { + Log.i( + TAG, + "Connected to CastingPlayer with deviceId: " + + targetCastingPlayer.getDeviceId()); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Connected to Casting Player with device name: " + + targetCastingPlayer.getDeviceName() + + "\n\n"); + connectionFragmentNextButton.setEnabled(true); + }); + } + }, + new MatterCallback() { + @Override + public void handle(MatterError err) { + Log.e(TAG, "CastingPLayer connection failed: " + err); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } + }); + +if (err.hasError()) +{ + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); +} +``` + On iOS, the Casting Client may call `verifyOrEstablishConnection` on the `MCCastingPlayer` object it wants to connect to and handle any `NSErrors` that may happen in the process. @@ -777,6 +830,8 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { ### Select an Endpoint on the Casting Player _{Complete Endpoint selection examples: [Linux](linux/simple-app-helper.cpp) | +[Android](android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java) +| [iOS](darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift)}_ On a successful connection with a `CastingPlayer`, a Casting Client may select @@ -803,6 +858,34 @@ if (it != endpoints.end()) } ``` +On Android, it can select an `Endpoint` as shown below. + +```java +private static final Integer SAMPLE_ENDPOINT_VID = 65521; + +private Endpoint selectEndpoint() +{ + Endpoint endpoint = null; + if(selectedCastingPlayer != null) + { + List endpoints = selectedCastingPlayer.getEndpoints(); + if (endpoints == null) + { + Log.e(TAG, "No Endpoints found on CastingPlayer"); + } + else + { + endpoint = endpoints + .stream() + .filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())) + .findFirst() + .get(); + } + } + return endpoint; +} +``` + On iOS, it can select an `MCEndpoint` similarly and as shown below. ```swift @@ -1045,11 +1128,8 @@ vendorIDAttribute!.read(nil) { context, before, after, err in ### Subscriptions -_{Complete Attribute subscription examples: -[Linux](linux/simple-app-helper.cpp)}_ - -_{Complete Attribute Read examples: [Linux](linux/simple-app-helper.cpp) | -[iOS](darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift)}_ +_{Complete Attribute subscription examples: [Linux](linux/simple-app-helper.cpp) +|[iOS](darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift)}_ A Casting Client may subscribe to an attribute on an `Endpoint` of the `CastingPlayer` to get data reports when the attributes change. diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java index 9db82781d855b8..6e54ff61b01c7e 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/MainActivity.java @@ -10,10 +10,12 @@ import com.chip.casting.DiscoveredNodeData; import com.chip.casting.TvCastingApp; import com.chip.casting.util.GlobalCastingConstants; -import com.chip.casting.util.PreferencesConfigurationManager; +import com.matter.casting.ActionSelectorFragment; import com.matter.casting.ConnectionExampleFragment; +import com.matter.casting.ContentLauncherLaunchURLExampleFragment; import com.matter.casting.DiscoveryExampleFragment; import com.matter.casting.InitializationExample; +import com.matter.casting.PreferencesConfigurationManager; import com.matter.casting.core.CastingPlayer; import java.util.Random; @@ -22,7 +24,8 @@ public class MainActivity extends AppCompatActivity ConnectionFragment.Callback, SelectClusterFragment.Callback, DiscoveryExampleFragment.Callback, - ConnectionExampleFragment.Callback { + ConnectionExampleFragment.Callback, + ActionSelectorFragment.Callback { private static final String TAG = MainActivity.class.getSimpleName(); @@ -33,6 +36,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); + Log.i(TAG, "ChipCastingSimplified = " + GlobalCastingConstants.ChipCastingSimplified); boolean ret = GlobalCastingConstants.ChipCastingSimplified ? InitializationExample.initAndStart(this.getApplicationContext()).hasNoError() @@ -73,9 +77,12 @@ public void handleCommissioningComplete() { @Override public void handleConnectionComplete(CastingPlayer castingPlayer) { Log.i(TAG, "MainActivity.handleConnectionComplete() called "); + showFragment(ActionSelectorFragment.newInstance(castingPlayer)); + } - // TODO: Implement in following PRs. Select Cluster Fragment. - // showFragment(SelectClusterFragment.newInstance(tvCastingApp)); + @Override + public void handleContentLauncherLaunchURLSelected(CastingPlayer selectedCastingPlayer) { + showFragment(ContentLauncherLaunchURLExampleFragment.newInstance(selectedCastingPlayer)); } @Override @@ -95,7 +102,10 @@ public void handleMediaPlaybackSelected() { @Override public void handleDisconnect() { - showFragment(CommissionerDiscoveryFragment.newInstance(tvCastingApp)); + showFragment( + GlobalCastingConstants.ChipCastingSimplified + ? DiscoveryExampleFragment.newInstance() + : CommissionerDiscoveryFragment.newInstance(tvCastingApp)); } /** diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/PreferencesConfigurationManager.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/PreferencesConfigurationManager.java deleted file mode 100644 index 5f7cb1cb7a2a16..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/util/PreferencesConfigurationManager.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2021-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 com.chip.casting.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import chip.platform.AndroidChipPlatformException; -import chip.platform.ConfigurationManager; -import chip.platform.KeyValueStoreManager; -import java.util.Base64; -import java.util.Map; -import java.util.UUID; - -/** Java interface for ConfigurationManager */ -public class PreferencesConfigurationManager implements ConfigurationManager { - - private final String TAG = KeyValueStoreManager.class.getSimpleName(); - private SharedPreferences preferences; - - public PreferencesConfigurationManager(Context context, String preferenceFileKey) { - preferences = context.getSharedPreferences(preferenceFileKey, Context.MODE_PRIVATE); - - try { - String keyUniqueId = getKey(kConfigNamespace_ChipFactory, kConfigKey_UniqueId); - if (!preferences.contains(keyUniqueId)) { - preferences - .edit() - .putString(keyUniqueId, UUID.randomUUID().toString().replaceAll("-", "")) - .apply(); - } - } catch (AndroidChipPlatformException e) { - e.printStackTrace(); - } - } - - @Override - public long readConfigValueLong(String namespace, String name) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - if (preferences.contains(key)) { - long value = preferences.getLong(key, Long.MAX_VALUE); - return value; - } else { - Log.d(TAG, "Key '" + key + "' not found in shared preferences"); - throw new AndroidChipPlatformException(); - } - } - - @Override - public String readConfigValueStr(String namespace, String name) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - if (preferences.contains(key)) { - String value = preferences.getString(key, null); - return value; - } else { - Log.d(TAG, "Key '" + key + "' not found in shared preferences"); - throw new AndroidChipPlatformException(); - } - } - - @Override - public byte[] readConfigValueBin(String namespace, String name) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - if (preferences.contains(key)) { - String value = preferences.getString(key, null); - byte[] byteValue = Base64.getDecoder().decode(value); - return byteValue; - } else { - Log.d(TAG, "Key '" + key + "' not found in shared preferences"); - throw new AndroidChipPlatformException(); - } - } - - @Override - public void writeConfigValueLong(String namespace, String name, long val) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - preferences.edit().putLong(key, val).apply(); - } - - @Override - public void writeConfigValueStr(String namespace, String name, String val) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - preferences.edit().putString(key, val).apply(); - } - - @Override - public void writeConfigValueBin(String namespace, String name, byte[] val) - throws AndroidChipPlatformException { - String key = getKey(namespace, name); - if (val != null) { - String valStr = Base64.getEncoder().encodeToString(val); - preferences.edit().putString(key, valStr).apply(); - } else { - preferences.edit().remove(key).apply(); - } - } - - @Override - public void clearConfigValue(String namespace, String name) throws AndroidChipPlatformException { - if (namespace != null && name != null) { - preferences.edit().remove(getKey(namespace, name)).apply(); - } else if (namespace != null && name == null) { - String pre = getKey(namespace, null); - SharedPreferences.Editor editor = preferences.edit(); - Map allEntries = preferences.getAll(); - for (Map.Entry entry : allEntries.entrySet()) { - String key = entry.getKey(); - if (key.startsWith(pre)) { - editor.remove(key); - } - } - editor.apply(); - } else if (namespace == null && name == null) { - preferences.edit().clear().apply(); - } - } - - @Override - public boolean configValueExists(String namespace, String name) - throws AndroidChipPlatformException { - return preferences.contains(getKey(namespace, name)); - } - - private String getKey(String namespace, String name) throws AndroidChipPlatformException { - if (namespace != null && name != null) { - return namespace + ":" + name; - } else if (namespace != null && name == null) { - return namespace + ":"; - } - - throw new AndroidChipPlatformException(); - } -} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java new file mode 100644 index 00000000000000..2906ad186d6054 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ActionSelectorFragment.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 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 com.matter.casting; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.R; +import com.matter.casting.core.CastingPlayer; + +/** An interstitial {@link Fragment} to select one of the supported media actions to perform */ +public class ActionSelectorFragment extends Fragment { + private static final String TAG = ActionSelectorFragment.class.getSimpleName(); + + private final CastingPlayer selectedCastingPlayer; + + private View.OnClickListener selectContentLauncherButtonClickListener; + private View.OnClickListener disconnectButtonClickListener; + + public ActionSelectorFragment(CastingPlayer selectedCastingPlayer) { + this.selectedCastingPlayer = selectedCastingPlayer; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param selectedCastingPlayer CastingPlayer that the casting app connected to + * @return A new instance of fragment SelectActionFragment. + */ + public static ActionSelectorFragment newInstance(CastingPlayer selectedCastingPlayer) { + return new ActionSelectorFragment(selectedCastingPlayer); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + ActionSelectorFragment.Callback callback = (ActionSelectorFragment.Callback) this.getActivity(); + this.selectContentLauncherButtonClickListener = + v -> { + Log.d(TAG, "handle() called on selectContentLauncherButtonClickListener"); + callback.handleContentLauncherLaunchURLSelected(selectedCastingPlayer); + }; + + this.disconnectButtonClickListener = + v -> { + Log.d(TAG, "Disconnecting from current casting player"); + selectedCastingPlayer.disconnect(); + callback.handleDisconnect(); + }; + + return inflater.inflate(R.layout.fragment_matter_action_selector, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Log.d(TAG, "ActionSelectorFragment.onViewCreated called"); + getView() + .findViewById(R.id.selectContentLauncherLaunchURLButton) + .setOnClickListener(selectContentLauncherButtonClickListener); + + getView().findViewById(R.id.disconnectButton).setOnClickListener(disconnectButtonClickListener); + } + + /** Interface for notifying the host. */ + public interface Callback { + /** Notifies listener to trigger transition on selection of Content Launcher cluster */ + void handleContentLauncherLaunchURLSelected(CastingPlayer selectedCastingPlayer); + + /** Notifies listener to trigger transition on click of the Disconnect button */ + void handleDisconnect(); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java index c6462cd52690e5..690a9b02e840bc 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ConnectionExampleFragment.java @@ -27,10 +27,9 @@ import androidx.fragment.app.Fragment; import com.R; import com.matter.casting.core.CastingPlayer; -import com.matter.casting.support.DeviceTypeStruct; import com.matter.casting.support.EndpointFilter; -import java.util.ArrayList; -import java.util.concurrent.CompletableFuture; +import com.matter.casting.support.MatterCallback; +import com.matter.casting.support.MatterError; import java.util.concurrent.Executors; /** A {@link Fragment} to Verify or establish a connection with a selected Casting Player. */ @@ -39,12 +38,16 @@ public class ConnectionExampleFragment extends Fragment { // Time (in sec) to keep the commissioning window open, if commissioning is required. // Must be >= 3 minutes. private static final long MIN_CONNECTION_TIMEOUT_SEC = 3 * 60; + private static final Integer DESIRED_ENDPOINT_VENDOR_ID = 65521; private final CastingPlayer targetCastingPlayer; private TextView connectionFragmentStatusTextView; private Button connectionFragmentNextButton; public ConnectionExampleFragment(CastingPlayer targetCastingPlayer) { - Log.i(TAG, "ConnectionExampleFragment() called with target CastingPlayer"); + Log.i( + TAG, + "ConnectionExampleFragment() called with target CastingPlayer ID: " + + targetCastingPlayer.getDeviceId()); this.targetCastingPlayer = targetCastingPlayer; } @@ -81,7 +84,11 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { connectionFragmentStatusTextView = getView().findViewById(R.id.connectionFragmentStatusText); connectionFragmentStatusTextView.setText( "Verifying or establishing connection with Casting Player with device name: " - + targetCastingPlayer.getDeviceName()); + + targetCastingPlayer.getDeviceName() + + "\nSetup Passcode: " + + InitializationExample.commissionableDataProvider.get().getSetupPasscode() + + "\nDiscriminator: " + + InitializationExample.commissionableDataProvider.get().getDiscriminator()); connectionFragmentNextButton = getView().findViewById(R.id.connectionFragmentNextButton); Callback callback = (ConnectionExampleFragment.Callback) this.getActivity(); @@ -94,48 +101,54 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { Executors.newSingleThreadExecutor() .submit( () -> { - Log.d(TAG, "onViewCreated() calling verifyOrEstablishConnection()"); + Log.d(TAG, "onViewCreated() calling CastingPlayer.verifyOrEstablishConnection()"); - EndpointFilter desiredEndpointFilter = - new EndpointFilter(null, 65521, new ArrayList()); - // The desired commissioning window timeout and EndpointFilter are optional. - CompletableFuture completableFuture = - targetCastingPlayer.VerifyOrEstablishConnection( - MIN_CONNECTION_TIMEOUT_SEC, desiredEndpointFilter); + EndpointFilter desiredEndpointFilter = new EndpointFilter(); + desiredEndpointFilter.vendorId = DESIRED_ENDPOINT_VENDOR_ID; - Log.d(TAG, "onViewCreated() verifyOrEstablishConnection() called"); - - completableFuture - .thenRun( - () -> { - Log.i( - TAG, - "CompletableFuture.thenRun(), connected to CastingPlayer with deviceId: " - + targetCastingPlayer.getDeviceId()); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Connected to Casting Player with device name: " - + targetCastingPlayer.getDeviceName()); - connectionFragmentNextButton.setEnabled(true); - }); - }) - .exceptionally( - exc -> { - Log.e( - TAG, - "CompletableFuture.exceptionally(), CastingPLayer connection failed: " - + exc.getMessage()); - getActivity() - .runOnUiThread( - () -> { - connectionFragmentStatusTextView.setText( - "Casting Player connection failed due to: " - + exc.getMessage()); - }); - return null; + MatterError err = + targetCastingPlayer.verifyOrEstablishConnection( + MIN_CONNECTION_TIMEOUT_SEC, + desiredEndpointFilter, + new MatterCallback() { + @Override + public void handle(Void v) { + Log.i( + TAG, + "Connected to CastingPlayer with deviceId: " + + targetCastingPlayer.getDeviceId()); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Connected to Casting Player with device name: " + + targetCastingPlayer.getDeviceName() + + "\n\n"); + connectionFragmentNextButton.setEnabled(true); + }); + } + }, + new MatterCallback() { + @Override + public void handle(MatterError err) { + Log.e(TAG, "CastingPlayer connection failed: " + err); + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } }); + + if (err.hasError()) { + getActivity() + .runOnUiThread( + () -> { + connectionFragmentStatusTextView.setText( + "Casting Player connection failed due to: " + err + "\n\n"); + }); + } }); } diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java new file mode 100644 index 00000000000000..c1bde0f9bb0323 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/ContentLauncherLaunchURLExampleFragment.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024 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 com.matter.casting; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import com.R; +import com.matter.casting.core.CastingPlayer; +import com.matter.casting.core.Endpoint; +import java.util.List; + +/** A {@link Fragment} to send Content Launcher LaunchURL command from the TV Casting App. */ +public class ContentLauncherLaunchURLExampleFragment extends Fragment { + private static final String TAG = ContentLauncherLaunchURLExampleFragment.class.getSimpleName(); + private static final Integer SAMPLE_ENDPOINT_VID = 65521; + + private final CastingPlayer selectedCastingPlayer; + + private View.OnClickListener launchUrlButtonClickListener; + + public ContentLauncherLaunchURLExampleFragment(CastingPlayer selectedCastingPlayer) { + this.selectedCastingPlayer = selectedCastingPlayer; + } + + /** + * Use this factory method to create a new instance of this fragment using the provided + * parameters. + * + * @param selectedCastingPlayer CastingPlayer that the casting app connected to + * @return A new instance of fragment ContentLauncherLaunchURLExampleFragment. + */ + public static ContentLauncherLaunchURLExampleFragment newInstance( + CastingPlayer selectedCastingPlayer) { + return new ContentLauncherLaunchURLExampleFragment(selectedCastingPlayer); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + this.launchUrlButtonClickListener = + v -> { + Endpoint endpoint = selectEndpoint(); + if (endpoint == null) { + Log.e( + TAG, + "No Endpoint with chosen vendorID: " + + SAMPLE_ENDPOINT_VID + + " found on CastingPlayer"); + return; + } + + // TODO: add command invocation API call + }; + return inflater.inflate(R.layout.fragment_content_launcher, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Log.d(TAG, "ContentLauncherLaunchURLExampleFragment.onViewCreated called"); + getView().findViewById(R.id.launchUrlButton).setOnClickListener(launchUrlButtonClickListener); + } + + private Endpoint selectEndpoint() { + Endpoint endpoint = null; + if (selectedCastingPlayer != null) { + List endpoints = selectedCastingPlayer.getEndpoints(); + if (endpoints == null) { + Log.e(TAG, "No Endpoints found on CastingPlayer"); + } else { + endpoint = + endpoints + .stream() + .filter(e -> SAMPLE_ENDPOINT_VID.equals(e.getVendorId())) + .findFirst() + .get(); + } + } + return endpoint; + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java index 67db95be2637df..b847e17f5b7026 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/DiscoveryExampleFragment.java @@ -203,8 +203,13 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { @Override public void onResume() { + Log.i(TAG, "onResume() called"); super.onResume(); - Log.i(TAG, "onResume() called. Calling startDiscovery()"); + MatterError err = + matterCastingPlayerDiscovery.removeCastingPlayerChangeListener(castingPlayerChangeListener); + if (err.hasError()) { + Log.e(TAG, "onResume() removeCastingPlayerChangeListener() err: " + err); + } if (!startDiscovery()) { Log.e(TAG, "onResume() Warning: startDiscovery() call Failed"); } @@ -253,13 +258,9 @@ private boolean startDiscovery() { matterDiscoveryMessageTextView.setText( getString(R.string.matter_discovery_message_discovering_text)); - Log.d( - TAG, - "startDiscovery() text set to: " - + getString(R.string.matter_discovery_message_discovering_text)); // TODO: In following PRs. Enable this to auto-stop discovery after stopDiscovery is - // implemented in the core Matter SKD DNS-SD API. + // implemented in the core Matter SDK DNS-SD API. // Schedule a service to stop discovery and remove the CastingPlayerChangeListener // Safe to call if discovery is not running // scheduledFutureTask = diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java index 0d2135e1a7bc66..aa602c79a66a94 100644 --- a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/InitializationExample.java @@ -19,7 +19,6 @@ import android.content.Context; import android.util.Log; import chip.platform.ConfigurationManager; -import com.chip.casting.util.PreferencesConfigurationManager; import com.matter.casting.core.CastingApp; import com.matter.casting.support.AppParameters; import com.matter.casting.support.CommissionableData; @@ -49,7 +48,7 @@ public byte[] get() { * DataProvider implementation for the Commissioning Data used by the SDK when the CastingApp goes * through commissioning */ - private static final DataProvider commissionableDataProvider = + static final DataProvider commissionableDataProvider = new DataProvider() { @Override public CommissionableData get() { diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/PreferencesConfigurationManager.java b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/PreferencesConfigurationManager.java new file mode 100644 index 00000000000000..1b6c4dfffe6e72 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/java/com/matter/casting/PreferencesConfigurationManager.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2021-2024 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 com.matter.casting; + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; +import chip.platform.AndroidChipPlatformException; +import chip.platform.ConfigurationManager; +import chip.platform.KeyValueStoreManager; +import java.util.Base64; +import java.util.Map; +import java.util.UUID; + +/** Java interface for ConfigurationManager */ +public class PreferencesConfigurationManager implements ConfigurationManager { + + private final String TAG = KeyValueStoreManager.class.getSimpleName(); + private SharedPreferences preferences; + + public PreferencesConfigurationManager(Context context, String preferenceFileKey) { + preferences = context.getSharedPreferences(preferenceFileKey, Context.MODE_PRIVATE); + + try { + String keyUniqueId = getKey(kConfigNamespace_ChipFactory, kConfigKey_UniqueId); + if (!preferences.contains(keyUniqueId)) { + preferences + .edit() + .putString(keyUniqueId, UUID.randomUUID().toString().replaceAll("-", "")) + .apply(); + } + } catch (AndroidChipPlatformException e) { + e.printStackTrace(); + } + } + + @Override + public long readConfigValueLong(String namespace, String name) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + switch (key) { + /** + * The unique id assigned by the device vendor to identify the product or device type. This + * number is scoped to the device vendor id. return a different value than + * src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ProductId: + return 0x8003; + + /** + * The default hardware version number assigned to the device or product by the device + * vendor. + * + *

Hardware versions are specific to a particular device vendor and product id, and + * typically correspond to a revision of the physical device, a change to its packaging, + * and/or a change to its marketing presentation. This value is generally *not* incremented + * for device software revisions. + * + *

This is a default value which is used when a hardware version has not been stored in + * device persistent storage (e.g. by a factory provisioning process). + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_HardwareVersion: + return 1; + + /** + * A monothonic number identifying the software version running on the device. + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_SoftwareVersion: + return 1; + + /** Matter Casting Video Client has device type ID 41 (i.e. 0x0029) */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_DeviceTypeId: + return 41; + } + + if (preferences.contains(key)) { + long value = preferences.getLong(key, Long.MAX_VALUE); + return value; + } else { + Log.d(TAG, "Key '" + key + "' not found in shared preferences"); + throw new AndroidChipPlatformException(); + } + } + + @Override + public String readConfigValueStr(String namespace, String name) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + + switch (key) { + /** + * Human readable name of the device model. return a different value than + * src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ProductName: + return "TEST_ANDROID_PRODUCT"; + + /** + * Human readable string identifying version of the product assigned by the device vendor. + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_HardwareVersionString: + return "TEST_ANDROID_VERSION"; + + /** + * A string identifying the software version running on the device. + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_SoftwareVersionString: + return "prerelease(android)"; + + /** + * The ManufacturingDate attribute SHALL specify the date that the Node was manufactured. + * The first 8 characters SHALL specify the date of manufacture of the Node in international + * date notation according to ISO 8601, i.e., YYYYMMDD, e.g., 20060814. The final 8 + * characters MAY include country, factory, line, shift or other related information at the + * option of the vendor. The format of this information is vendor defined. + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ManufacturingDate: + return "2021-12-06"; + + /** + * Enables the use of a hard-coded default serial number if none * is found in Chip NV + * storage. + * + *

return a different value than src/include/platform/CHIPDeviceConfig.h for debug + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_SerialNum: + return "TEST_ANDROID_SN"; + + /** + * The PartNumber attribute SHALL specify a human-readable (displayable) vendor assigned + * part number for the Node whose meaning and numbering scheme is vendor defined. Multiple + * products (and hence PartNumbers) can share a ProductID. For instance, there may be + * different packaging (with different PartNumbers) for different regions; also different + * colors of a product might share the ProductID but may have a different PartNumber. + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_PartNumber: + return "TEST_ANDROID_PRODUCT_BLUE"; + + /** + * The ProductURL attribute SHALL specify a link to a product specific web page. The syntax + * of the ProductURL attribute SHALL follow the syntax as specified in RFC 3986. The + * specified URL SHOULD resolve to a maintained web page available for the lifetime of the + * product. The maximum length of the ProductUrl attribute is 256 ASCII characters. + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ProductURL: + return "https://buildwithmatter.com/"; + + /** + * The ProductLabel attribute SHALL specify a vendor specific human readable (displayable) + * product label. The ProductLabel attribute MAY be used to provide a more user-friendly + * value than that represented by the ProductName attribute. The ProductLabel attribute + * SHOULD NOT include the name of the vendor as defined within the VendorName attribute. + */ + case kConfigNamespace_ChipFactory + ":" + kConfigKey_ProductLabel: + return "X10"; + } + + if (preferences.contains(key)) { + String value = preferences.getString(key, null); + return value; + } else { + Log.d(TAG, "Key '" + key + "' not found in shared preferences"); + throw new AndroidChipPlatformException(); + } + } + + @Override + public byte[] readConfigValueBin(String namespace, String name) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + if (preferences.contains(key)) { + String value = preferences.getString(key, null); + byte[] byteValue = Base64.getDecoder().decode(value); + return byteValue; + } else { + Log.d(TAG, "Key '" + key + "' not found in shared preferences"); + throw new AndroidChipPlatformException(); + } + } + + @Override + public void writeConfigValueLong(String namespace, String name, long val) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + preferences.edit().putLong(key, val).apply(); + } + + @Override + public void writeConfigValueStr(String namespace, String name, String val) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + preferences.edit().putString(key, val).apply(); + } + + @Override + public void writeConfigValueBin(String namespace, String name, byte[] val) + throws AndroidChipPlatformException { + String key = getKey(namespace, name); + if (val != null) { + String valStr = Base64.getEncoder().encodeToString(val); + preferences.edit().putString(key, valStr).apply(); + } else { + preferences.edit().remove(key).apply(); + } + } + + @Override + public void clearConfigValue(String namespace, String name) throws AndroidChipPlatformException { + if (namespace != null && name != null) { + preferences.edit().remove(getKey(namespace, name)).apply(); + } else if (namespace != null && name == null) { + String pre = getKey(namespace, null); + SharedPreferences.Editor editor = preferences.edit(); + Map allEntries = preferences.getAll(); + for (Map.Entry entry : allEntries.entrySet()) { + String key = entry.getKey(); + if (key.startsWith(pre)) { + editor.remove(key); + } + } + editor.apply(); + } else if (namespace == null && name == null) { + preferences.edit().clear().apply(); + } + } + + @Override + public boolean configValueExists(String namespace, String name) + throws AndroidChipPlatformException { + return preferences.contains(getKey(namespace, name)); + } + + private String getKey(String namespace, String name) throws AndroidChipPlatformException { + if (namespace != null && name != null) { + return namespace + ":" + name; + } else if (namespace != null && name == null) { + return namespace + ":"; + } + + throw new AndroidChipPlatformException(); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java index 0823920503d9db..f79eab859a8ce3 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingApp.java @@ -37,6 +37,8 @@ */ public final class CastingApp { private static final String TAG = CastingApp.class.getSimpleName(); + private static final long BROWSE_SERVICE_TIMEOUT = 2500; + private static final long RESOLVE_SERVICE_TIMEOUT = 3000; private static CastingApp sInstance; @@ -75,8 +77,9 @@ public MatterError initialize(AppParameters appParameters) { new AndroidBleManager(), new PreferencesKeyValueStoreManager(appParameters.getApplicationContext()), appParameters.getConfigurationManagerProvider().get(), - new NsdManagerServiceResolver(applicationContext, nsdManagerResolverAvailState), - new NsdManagerServiceBrowser(applicationContext), + new NsdManagerServiceResolver( + applicationContext, nsdManagerResolverAvailState, RESOLVE_SERVICE_TIMEOUT), + new NsdManagerServiceBrowser(applicationContext, BROWSE_SERVICE_TIMEOUT), new ChipMdnsCallbackImpl(), new DiagnosticDataProviderImpl(applicationContext)); @@ -147,6 +150,16 @@ public MatterError stop() { return MatterError.NO_ERROR; } + /** @brief Tears down all active subscriptions. */ + public native MatterError shutdownAllSubscriptions(); + + /** + * Clears app cache that contains the information about CastingPlayers previously connected to + * + * @return + */ + public native MatterError clearCache(); + /** * Sets DeviceAttestationCrdentials provider and RotatingDeviceIdUniqueId * diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingAppState.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingAppState.java index 89ddef7aa0dbf6..9f2e952216f067 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingAppState.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingAppState.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2024 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 com.matter.casting.core; /** Represents the state of the CastingApp */ diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java index 723f1b8e93b1a5..39db6488fa8ed8 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/CastingPlayer.java @@ -17,9 +17,10 @@ package com.matter.casting.core; import com.matter.casting.support.EndpointFilter; +import com.matter.casting.support.MatterCallback; +import com.matter.casting.support.MatterError; import java.net.InetAddress; import java.util.List; -import java.util.concurrent.CompletableFuture; /** * The CastingPlayer interface defines a Matter commissioner that is able to play media to a @@ -48,6 +49,8 @@ public interface CastingPlayer { long getDeviceType(); + List getEndpoints(); + @Override String toString(); @@ -68,27 +71,30 @@ public interface CastingPlayer { * that the client wants to interact with after commissioning. If this value is passed in, the * VerifyOrEstablishConnection will force User Directed Commissioning, in case the desired * Endpoint is not found in the on device CastingStore. - * @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed. - * The CompletableFuture will be completed with a Void value if the - * VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be - * completed with an Exception. The Exception will be of type - * com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the - * CastingException will contain the error code and message from the CastingApp. + * @param successCallback called when the connection is established successfully + * @param failureCallback called with MatterError when the connection is fails to establish + * @return MatterError - Matter.NO_ERROR if request submitted successfully, otherwise a + * MatterError object corresponding to the error */ - CompletableFuture VerifyOrEstablishConnection( - long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter); + MatterError verifyOrEstablishConnection( + long commissioningWindowTimeoutSec, + EndpointFilter desiredEndpointFilter, + MatterCallback successCallback, + MatterCallback failureCallback); /** * Verifies that a connection exists with this CastingPlayer, or triggers a new session request. * If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on * disk, this will execute the user directed commissioning process. * - * @return A CompletableFuture that completes when the VerifyOrEstablishConnection is completed. - * The CompletableFuture will be completed with a Void value if the - * VerifyOrEstablishConnection is successful. Otherwise, the CompletableFuture will be - * completed with an Exception. The Exception will be of type - * com.matter.casting.core.CastingException. If the VerifyOrEstablishConnection fails, the - * CastingException will contain the error code and message from the CastingApp. + * @param successCallback called when the connection is established successfully + * @param failureCallback called with MatterError when the connection is fails to establish + * @return MatterError - Matter.NO_ERROR if request submitted successfully, otherwise a + * MatterError object corresponding to the error */ - CompletableFuture VerifyOrEstablishConnection(); + MatterError verifyOrEstablishConnection( + MatterCallback successCallback, MatterCallback failureCallback); + + /** @brief Sets the internal connection state of this CastingPlayer to "disconnected" */ + void disconnect(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java new file mode 100644 index 00000000000000..6d1b63555aad08 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/Endpoint.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024 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 com.matter.casting.core; + +import com.matter.casting.support.DeviceTypeStruct; +import java.util.List; + +public interface Endpoint { + int getId(); + + int getVendorId(); + + int getProductId(); + + List getDeviceTypeList(); + + CastingPlayer getCastingPlayer(); +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java index d5d93c3204ec34..dd4bd0ba6531c6 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterCastingPlayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Project CHIP Authors + * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,10 +17,11 @@ package com.matter.casting.core; import com.matter.casting.support.EndpointFilter; +import com.matter.casting.support.MatterCallback; +import com.matter.casting.support.MatterError; import java.net.InetAddress; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletableFuture; /** * A Matter Casting Player represents a Matter commissioner that is able to play media to a physical @@ -130,6 +131,9 @@ public long getDeviceType() { return this.deviceType; } + @Override + public native List getEndpoints(); + @Override public String toString() { return this.deviceId; @@ -167,8 +171,11 @@ public boolean equals(Object o) { * CastingException will contain the error code and message from the CastingApp. */ @Override - public native CompletableFuture VerifyOrEstablishConnection( - long commissioningWindowTimeoutSec, EndpointFilter desiredEndpointFilter); + public native MatterError verifyOrEstablishConnection( + long commissioningWindowTimeoutSec, + EndpointFilter desiredEndpointFilter, + MatterCallback successCallback, + MatterCallback failureCallback); /** * Verifies that a connection exists with this CastingPlayer, or triggers a new session request. @@ -183,7 +190,12 @@ public native CompletableFuture VerifyOrEstablishConnection( * CastingException will contain the error code and message from the CastingApp. */ @Override - public CompletableFuture VerifyOrEstablishConnection() { - return VerifyOrEstablishConnection(MIN_CONNECTION_TIMEOUT_SEC, null); + public MatterError verifyOrEstablishConnection( + MatterCallback successCallback, MatterCallback failureCallback) { + return verifyOrEstablishConnection( + MIN_CONNECTION_TIMEOUT_SEC, null, successCallback, failureCallback); } + + @Override + public native void disconnect(); } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java new file mode 100644 index 00000000000000..b9dd564d6ff95f --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/core/MatterEndpoint.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 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 com.matter.casting.core; + +import com.matter.casting.support.DeviceTypeStruct; +import java.util.List; +import java.util.Objects; + +public class MatterEndpoint implements Endpoint { + private static final String TAG = MatterEndpoint.class.getSimpleName(); + protected long _cppEndpoint; + + @Override + public native int getId(); + + @Override + public native int getVendorId(); + + @Override + public native int getProductId(); + + @Override + public native List getDeviceTypeList(); + + @Override + public native CastingPlayer getCastingPlayer(); + + @Override + public String toString() { + return "MatterEndpoint{" + "id=" + getId() + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MatterEndpoint that = (MatterEndpoint) o; + return getId() == that.getId(); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/EndpointFilter.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/EndpointFilter.java index 1152e48b95890c..833c24fdc57544 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/EndpointFilter.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/EndpointFilter.java @@ -26,6 +26,8 @@ public class EndpointFilter { public Integer vendorId; public List requiredDeviceTypes; + public EndpointFilter() {} + public EndpointFilter( Integer productId, Integer vendorId, List requiredDeviceTypes) { this.productId = productId; diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/MatterCallback.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/MatterCallback.java new file mode 100644 index 00000000000000..2d1f01dedb262d --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/matter/casting/support/MatterCallback.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 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 com.matter.casting.support; + +import android.util.Log; + +public abstract class MatterCallback { + private static final String TAG = MatterCallback.class.getSimpleName(); + + public abstract void handle(R response); + + protected final void handleInternal(R response) { + try { + handle(response); + } catch (Throwable t) { + Log.e(TAG, "MatterCallback::Caught an unhandled Throwable from the client: " + t); + } + } +} diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp index e3a4c79b956822..81a42115070da7 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp @@ -19,7 +19,7 @@ #include "CastingApp-JNI.h" #include "../JNIDACProvider.h" -#include "../support/ErrorConverter-JNI.h" +#include "../support/Converters-JNI.h" #include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" // from tv-casting-common @@ -48,15 +48,15 @@ JNI_METHOD(jobject, finishInitialization)(JNIEnv *, jobject, jobject jAppParamet { chip::DeviceLayer::StackLock lock; ChipLogProgress(AppServer, "JNI_METHOD CastingApp-JNI::finishInitialization() called"); - VerifyOrReturnValue(jAppParameters != nullptr, support::createJMatterError(CHIP_ERROR_INVALID_ARGUMENT)); + VerifyOrReturnValue(jAppParameters != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT)); CHIP_ERROR err = CHIP_NO_ERROR; jobject jUniqueIdProvider = extractJAppParameter(jAppParameters, "getRotatingDeviceIdUniqueIdProvider", "()Lcom/matter/casting/support/DataProvider;"); - VerifyOrReturnValue(jUniqueIdProvider != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(jUniqueIdProvider != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); support::RotatingDeviceIdUniqueIdProviderJNI * uniqueIdProvider = new support::RotatingDeviceIdUniqueIdProviderJNI(); err = uniqueIdProvider->Initialize(jUniqueIdProvider); - VerifyOrReturnValue(err == CHIP_NO_ERROR, support::createJMatterError(CHIP_ERROR_INVALID_ARGUMENT)); + VerifyOrReturnValue(err == CHIP_NO_ERROR, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT)); // set the RotatingDeviceIdUniqueId #if CHIP_ENABLE_ROTATING_DEVICE_ID @@ -69,13 +69,13 @@ JNI_METHOD(jobject, finishInitialization)(JNIEnv *, jobject, jobject jAppParamet // get the DACProvider jobject jDACProvider = extractJAppParameter(jAppParameters, "getDacProvider", "()Lcom/matter/casting/support/DACProvider;"); - VerifyOrReturnValue(jDACProvider != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(jDACProvider != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); // set the DACProvider JNIDACProvider * dacProvider = new JNIDACProvider(jDACProvider); chip::Credentials::SetDeviceAttestationCredentialsProvider(dacProvider); - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } JNI_METHOD(jobject, finishStartup)(JNIEnv *, jobject) @@ -92,17 +92,35 @@ JNI_METHOD(jobject, finishStartup)(JNIEnv *, jobject) // Initialize binding handlers err = chip::BindingManager::GetInstance().Init( { &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() }); - VerifyOrReturnValue(err == CHIP_NO_ERROR, support::createJMatterError(err), + VerifyOrReturnValue(err == CHIP_NO_ERROR, support::convertMatterErrorFromCppToJava(err), ChipLogError(AppServer, "Failed to init BindingManager %" CHIP_ERROR_FORMAT, err.Format())); // TODO: Set FabricDelegate // chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(&mPersistenceManager); err = chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(support::ChipDeviceEventHandler::Handle, 0); - VerifyOrReturnValue(err == CHIP_NO_ERROR, support::createJMatterError(err), + VerifyOrReturnValue(err == CHIP_NO_ERROR, support::convertMatterErrorFromCppToJava(err), ChipLogError(AppServer, "Failed to register ChipDeviceEventHandler %" CHIP_ERROR_FORMAT, err.Format())); - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); +} + +JNI_METHOD(jobject, shutdownAllSubscriptions)(JNIEnv * env, jobject) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "JNI_METHOD CastingApp-JNI::shutdownAllSubscriptions called"); + + CHIP_ERROR err = matter::casting::core::CastingApp::GetInstance()->ShutdownAllSubscriptions(); + return support::convertMatterErrorFromCppToJava(err); +} + +JNI_METHOD(jobject, clearCache)(JNIEnv * env, jobject) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "JNI_METHOD CastingApp-JNI::clearCache called"); + + CHIP_ERROR err = matter::casting::core::CastingApp::GetInstance()->ClearCache(); + return support::convertMatterErrorFromCppToJava(err); } jobject extractJAppParameter(jobject jAppParameters, const char * methodName, const char * methodSig) diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp deleted file mode 100644 index ba5a8e765908f1..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2024 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. - * - */ - -#include "CastingPlayer-JNI.h" - -#include "../JNIDACProvider.h" -#include "../support/CastingPlayerConverter-JNI.h" -#include "../support/ErrorConverter-JNI.h" -#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" -#include "core/CastingApp.h" // from tv-casting-common -#include "core/CastingPlayer.h" // from tv-casting-common -#include "core/CastingPlayerDiscovery.h" // from tv-casting-common - -#include -#include -#include -#include -#include - -using namespace chip; - -#define JNI_METHOD(RETURN, METHOD_NAME) \ - extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterCastingPlayer_##METHOD_NAME - -namespace matter { -namespace casting { -namespace core { - -JNI_METHOD(jobject, VerifyOrEstablishConnection) -(JNIEnv * env, jobject thiz, jlong commissioningWindowTimeoutSec, jobject desiredEndpointFilterJavaObject) -{ - chip::DeviceLayer::StackLock lock; - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() called with a timeout of: %ld seconds", - static_cast(commissioningWindowTimeoutSec)); - - // Convert the CastingPlayer jlong to a CastingPlayer pointer - jclass castingPlayerClass = env->GetObjectClass(thiz); - jfieldID _cppCastingPlayerFieldId = env->GetFieldID(castingPlayerClass, "_cppCastingPlayer", "J"); - VerifyOrReturnValue( - _cppCastingPlayerFieldId != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() _cppCastingPlayerFieldId == nullptr")); - - jlong _cppCastingPlayerValue = env->GetLongField(thiz, _cppCastingPlayerFieldId); - CastingPlayer * castingPlayer = reinterpret_cast(_cppCastingPlayerValue); - VerifyOrReturnValue(castingPlayer != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() castingPlayer == nullptr")); - - // Create a new Java CompletableFuture - jclass completableFutureClass = env->FindClass("java/util/concurrent/CompletableFuture"); - jmethodID completableFutureConstructor = env->GetMethodID(completableFutureClass, "", "()V"); - jobject completableFutureObj = env->NewObject(completableFutureClass, completableFutureConstructor); - jobject completableFutureObjGlobalRef = env->NewGlobalRef(completableFutureObj); - VerifyOrReturnValue( - completableFutureObjGlobalRef != nullptr, nullptr, - ChipLogError(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() completableFutureObjGlobalRef == nullptr")); - - ConnectCallback callback = [completableFutureObjGlobalRef](CHIP_ERROR err, CastingPlayer * playerPtr) { - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() ConnectCallback called"); - VerifyOrReturn(completableFutureObjGlobalRef != nullptr, - ChipLogError(AppServer, "ConnectCallback, completableFutureObjGlobalRef == nullptr")); - - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - VerifyOrReturn(env != nullptr, ChipLogError(AppServer, "ConnectCallback, env == nullptr")); - // Ensures proper cleanup of local references to Java objects. - JniLocalReferenceScope scope(env); - // Ensures proper cleanup of global references to Java objects. - JniGlobalRefWrapper globalRefWrapper(completableFutureObjGlobalRef); - - jclass completableFutureClass = env->FindClass("java/util/concurrent/CompletableFuture"); - VerifyOrReturn(completableFutureClass != nullptr, - ChipLogError(AppServer, "ConnectCallback, completableFutureClass == nullptr"); - env->DeleteGlobalRef(completableFutureObjGlobalRef);); - - if (err == CHIP_NO_ERROR) - { - ChipLogProgress(AppServer, "ConnectCallback, Connected to Casting Player with device ID: %s", playerPtr->GetId()); - jmethodID completeMethod = env->GetMethodID(completableFutureClass, "complete", "(Ljava/lang/Object;)Z"); - VerifyOrReturn(completeMethod != nullptr, ChipLogError(AppServer, "ConnectCallback, completeMethod == nullptr")); - - chip::DeviceLayer::StackUnlock unlock; - env->CallBooleanMethod(completableFutureObjGlobalRef, completeMethod, nullptr); - } - else - { - ChipLogError(AppServer, "ConnectCallback, connection error: %" CHIP_ERROR_FORMAT, err.Format()); - jmethodID completeExceptionallyMethod = - env->GetMethodID(completableFutureClass, "completeExceptionally", "(Ljava/lang/Throwable;)Z"); - VerifyOrReturn(completeExceptionallyMethod != nullptr, - ChipLogError(AppServer, "ConnectCallback, completeExceptionallyMethod == nullptr")); - - // Create a Throwable object (e.g., RuntimeException) to pass to completeExceptionallyMethod - jclass throwableClass = env->FindClass("java/lang/RuntimeException"); - VerifyOrReturn(throwableClass != nullptr, ChipLogError(AppServer, "ConnectCallback, throwableClass == nullptr")); - jmethodID throwableConstructor = env->GetMethodID(throwableClass, "", "(Ljava/lang/String;)V"); - VerifyOrReturn(throwableConstructor != nullptr, - ChipLogError(AppServer, "ConnectCallback, throwableConstructor == nullptr")); - jstring errorMessage = env->NewStringUTF(err.Format()); - VerifyOrReturn(errorMessage != nullptr, ChipLogError(AppServer, "ConnectCallback, errorMessage == nullptr")); - jobject throwableObject = env->NewObject(throwableClass, throwableConstructor, errorMessage); - VerifyOrReturn(throwableObject != nullptr, ChipLogError(AppServer, "ConnectCallback, throwableObject == nullptr")); - - chip::DeviceLayer::StackUnlock unlock; - env->CallBooleanMethod(completableFutureObjGlobalRef, completeExceptionallyMethod, throwableObject); - } - }; - - if (desiredEndpointFilterJavaObject == nullptr) - { - ChipLogProgress(AppServer, - "CastingPlayer-JNI::VerifyOrEstablishConnection() calling CastingPlayer::VerifyOrEstablishConnection() on " - "Casting Player with device ID: %s", - castingPlayer->GetId()); - castingPlayer->VerifyOrEstablishConnection(callback, static_cast(commissioningWindowTimeoutSec)); - } - else - { - // Convert the EndpointFilter Java class to a C++ EndpointFilter - jclass endpointFilterJavaClass = env->GetObjectClass(desiredEndpointFilterJavaObject); - jfieldID vendorIdFieldId = env->GetFieldID(endpointFilterJavaClass, "vendorId", "Ljava/lang/Integer;"); - jfieldID productIdFieldId = env->GetFieldID(endpointFilterJavaClass, "productId", "Ljava/lang/Integer;"); - jobject vendorIdIntegerObject = env->GetObjectField(desiredEndpointFilterJavaObject, vendorIdFieldId); - jobject productIdIntegerObject = env->GetObjectField(desiredEndpointFilterJavaObject, productIdFieldId); - // jfieldID requiredDeviceTypesFieldId = env->GetFieldID(endpointFilterJavaClass, "requiredDeviceTypes", - // "Ljava/util/List;"); - - matter::casting::core::EndpointFilter desiredEndpointFilter; - // Value of 0 means unspecified - desiredEndpointFilter.vendorId = vendorIdIntegerObject != nullptr - ? static_cast(env->CallIntMethod( - vendorIdIntegerObject, env->GetMethodID(env->GetObjectClass(vendorIdIntegerObject), "intValue", "()I"))) - : 0; - desiredEndpointFilter.productId = productIdIntegerObject != nullptr - ? static_cast(env->CallIntMethod( - productIdIntegerObject, env->GetMethodID(env->GetObjectClass(productIdIntegerObject), "intValue", "()I"))) - : 0; - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() desiredEndpointFilter.vendorId: %d", - desiredEndpointFilter.vendorId); - ChipLogProgress(AppServer, "CastingPlayer-JNI::VerifyOrEstablishConnection() desiredEndpointFilter.productId: %d", - desiredEndpointFilter.productId); - // TODO: In following PRs. Translate the Java requiredDeviceTypes list to a C++ requiredDeviceTypes vector. For now we're - // passing an empty list of DeviceTypeStruct. - - ChipLogProgress(AppServer, - "CastingPlayer-JNI::VerifyOrEstablishConnection() calling " - "CastingPlayer::VerifyOrEstablishConnection() on Casting Player with device ID: %s", - castingPlayer->GetId()); - castingPlayer->VerifyOrEstablishConnection(callback, static_cast(commissioningWindowTimeoutSec), - desiredEndpointFilter); - } - - return completableFutureObjGlobalRef; -} - -}; // namespace core -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp index 5838d4039ae6a3..5981ab80bbd52e 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayerDiscovery-JNI.cpp @@ -19,8 +19,7 @@ #include "CastingPlayerDiscovery-JNI.h" #include "../JNIDACProvider.h" -#include "../support/CastingPlayerConverter-JNI.h" -#include "../support/ErrorConverter-JNI.h" +#include "../support/Converters-JNI.h" #include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" #include "core/CastingApp.h" // from tv-casting-common #include "core/CastingPlayerDiscovery.h" // from tv-casting-common @@ -81,7 +80,7 @@ class DiscoveryDelegateImpl : public DiscoveryDelegate "CastingPlayerDiscovery-JNI::DiscoveryDelegateImpl::HandleOnAdded() Warning: Not set, " "onAddedCallbackJavaMethodID == nullptr")); - jobject matterCastingPlayerJavaObject = support::createJCastingPlayer(player); + jobject matterCastingPlayerJavaObject = support::convertCastingPlayerFromCppToJava(player); VerifyOrReturn(matterCastingPlayerJavaObject != nullptr, ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::DiscoveryDelegateImpl::HandleOnAdded() Warning: Could not create " @@ -108,7 +107,7 @@ class DiscoveryDelegateImpl : public DiscoveryDelegate "CastingPlayerDiscovery-JNI::DiscoveryDelegateImpl::HandleOnUpdated() Warning: Not set, " "onChangedCallbackJavaMethodID == nullptr")); - jobject matterCastingPlayerJavaObject = support::createJCastingPlayer(player); + jobject matterCastingPlayerJavaObject = support::convertCastingPlayerFromCppToJava(player); VerifyOrReturn(matterCastingPlayerJavaObject != nullptr, ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::DiscoveryDelegateImpl::HandleOnUpdated() Warning: Could not " @@ -160,10 +159,10 @@ JNI_METHOD(jobject, startDiscovery)(JNIEnv * env, jobject, jobject targetDeviceT if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CastingPlayerDiscovery-JNI startDiscovery() err: %" CHIP_ERROR_FORMAT, err.Format()); - return support::createJMatterError(err); + return support::convertMatterErrorFromCppToJava(err); } - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } JNI_METHOD(jobject, stopDiscovery)(JNIEnv * env, jobject) @@ -176,53 +175,55 @@ JNI_METHOD(jobject, stopDiscovery)(JNIEnv * env, jobject) if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::StopDiscovery() err: %" CHIP_ERROR_FORMAT, err.Format()); - return support::createJMatterError(err); + return support::convertMatterErrorFromCppToJava(err); } - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } JNI_METHOD(jobject, addCastingPlayerChangeListener)(JNIEnv * env, jobject, jobject castingPlayerChangeListenerJavaObject) { chip::DeviceLayer::StackLock lock; ChipLogProgress(AppServer, "CastingPlayerDiscovery-JNI::addCastingPlayerChangeListener() called"); - VerifyOrReturnValue(castingPlayerChangeListenerJavaObject != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(castingPlayerChangeListenerJavaObject != nullptr, + support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); if (DiscoveryDelegateImpl::GetInstance()->castingPlayerChangeListenerJavaObject.HasValidObjectRef()) { ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::addCastingPlayerChangeListener() Warning: Call removeCastingPlayerChangeListener " "before adding a new one"); - return support::createJMatterError(CHIP_ERROR_INCORRECT_STATE); + return support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE); } // Get the class and method IDs for the CastingPlayerChangeListener Java class jclass castingPlayerChangeListenerJavaClass = env->GetObjectClass(castingPlayerChangeListenerJavaObject); - VerifyOrReturnValue(castingPlayerChangeListenerJavaClass != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(castingPlayerChangeListenerJavaClass != nullptr, + support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); jmethodID onAddedJavaMethodID = env->GetMethodID(castingPlayerChangeListenerJavaClass, "_onAdded", "(Lcom/matter/casting/core/CastingPlayer;)V"); - VerifyOrReturnValue(onAddedJavaMethodID != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(onAddedJavaMethodID != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); jmethodID onChangedJavaMethodID = env->GetMethodID(castingPlayerChangeListenerJavaClass, "_onChanged", "(Lcom/matter/casting/core/CastingPlayer;)V"); - VerifyOrReturnValue(onChangedJavaMethodID != nullptr, support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + VerifyOrReturnValue(onChangedJavaMethodID != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); // jmethodID onRemovedJavaMethodID = env->GetMethodID(castingPlayerChangeListenerJavaClass, "_onRemoved", // "(Lcom/matter/casting/core/CastingPlayer;)V"); VerifyOrReturnValue(onRemovedJavaMethodID != nullptr, - // support::createJMatterError(CHIP_ERROR_INCORRECT_STATE)); + // support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE)); // Set Java callbacks in the DiscoveryDelegateImpl Singleton CHIP_ERROR err = DiscoveryDelegateImpl::GetInstance()->castingPlayerChangeListenerJavaObject.Init(castingPlayerChangeListenerJavaObject); if (err != CHIP_NO_ERROR) { - return support::createJMatterError(err); + return support::convertMatterErrorFromCppToJava(err); } DiscoveryDelegateImpl::GetInstance()->onAddedCallbackJavaMethodID = onAddedJavaMethodID; DiscoveryDelegateImpl::GetInstance()->onChangedCallbackJavaMethodID = onChangedJavaMethodID; // DiscoveryDelegateImpl::GetInstance()->onRemovedCallbackJavaMethodID = onRemovedJavaMethodID; - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } JNI_METHOD(jobject, removeCastingPlayerChangeListener)(JNIEnv * env, jobject, jobject castingPlayerChangeListenerJavaObject) @@ -243,14 +244,14 @@ JNI_METHOD(jobject, removeCastingPlayerChangeListener)(JNIEnv * env, jobject, jo DiscoveryDelegateImpl::GetInstance()->onChangedCallbackJavaMethodID = nullptr; // DiscoveryDelegateImpl::GetInstance()->onRemovedCallbackJavaMethodID = nullptr; - return support::createJMatterError(CHIP_NO_ERROR); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); } else { ChipLogError(AppServer, "CastingPlayerDiscovery-JNI::removeCastingPlayerChangeListener() Warning: Cannot remove listener. Received a " "different CastingPlayerChangeListener object"); - return support::createJMatterError(CHIP_ERROR_INCORRECT_STATE); + return support::convertMatterErrorFromCppToJava(CHIP_ERROR_INCORRECT_STATE); } } @@ -269,7 +270,7 @@ JNI_METHOD(jobject, getCastingPlayers)(JNIEnv * env, jobject) for (const auto & player : castingPlayersList) { - jobject matterCastingPlayerJavaObject = support::createJCastingPlayer(player); + jobject matterCastingPlayerJavaObject = support::convertCastingPlayerFromCppToJava(player); if (matterCastingPlayerJavaObject != nullptr) { jboolean added = env->CallBooleanMethod(arrayList, addMethod, matterCastingPlayerJavaObject); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp new file mode 100644 index 00000000000000..caa64b4ac8d717 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2024 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. + * + */ + +#include "MatterCastingPlayer-JNI.h" + +#include "../JNIDACProvider.h" +#include "../support/Converters-JNI.h" +#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" +#include "core/CastingApp.h" // from tv-casting-common +#include "core/CastingPlayer.h" // from tv-casting-common +#include "core/CastingPlayerDiscovery.h" // from tv-casting-common + +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterCastingPlayer_##METHOD_NAME + +namespace matter { +namespace casting { +namespace core { + +MatterCastingPlayerJNI MatterCastingPlayerJNI::sInstance; + +JNI_METHOD(jobject, verifyOrEstablishConnection) +(JNIEnv * env, jobject thiz, jlong commissioningWindowTimeoutSec, jobject desiredEndpointFilterJavaObject, jobject jSuccessCallback, + jobject jFailureCallback) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::verifyOrEstablishConnection() called with a timeout of: %ld seconds", + static_cast(commissioningWindowTimeoutSec)); + + CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz); + VerifyOrReturnValue(castingPlayer != nullptr, support::convertMatterErrorFromCppToJava(CHIP_ERROR_INVALID_ARGUMENT)); + + matter::casting::core::EndpointFilter desiredEndpointFilter; + if (desiredEndpointFilterJavaObject != nullptr) + { + // Convert the EndpointFilter Java class to a C++ EndpointFilter + jclass endpointFilterJavaClass = env->GetObjectClass(desiredEndpointFilterJavaObject); + jfieldID vendorIdFieldId = env->GetFieldID(endpointFilterJavaClass, "vendorId", "Ljava/lang/Integer;"); + jfieldID productIdFieldId = env->GetFieldID(endpointFilterJavaClass, "productId", "Ljava/lang/Integer;"); + jobject vendorIdIntegerObject = env->GetObjectField(desiredEndpointFilterJavaObject, vendorIdFieldId); + jobject productIdIntegerObject = env->GetObjectField(desiredEndpointFilterJavaObject, productIdFieldId); + // jfieldID requiredDeviceTypesFieldId = env->GetFieldID(endpointFilterJavaClass, "requiredDeviceTypes", + // "Ljava/util/List;"); + + // Value of 0 means unspecified + desiredEndpointFilter.vendorId = vendorIdIntegerObject != nullptr + ? static_cast(env->CallIntMethod( + vendorIdIntegerObject, env->GetMethodID(env->GetObjectClass(vendorIdIntegerObject), "intValue", "()I"))) + : 0; + desiredEndpointFilter.productId = productIdIntegerObject != nullptr + ? static_cast(env->CallIntMethod( + productIdIntegerObject, env->GetMethodID(env->GetObjectClass(productIdIntegerObject), "intValue", "()I"))) + : 0; + // TODO: In following PRs. Translate the Java requiredDeviceTypes list to a C++ requiredDeviceTypes vector. For now we're + // passing an empty list of DeviceTypeStruct. + } + + MatterCastingPlayerJNIMgr().mConnectionSuccessHandler.SetUp(env, jSuccessCallback); + MatterCastingPlayerJNIMgr().mConnectionFailureHandler.SetUp(env, jFailureCallback); + castingPlayer->VerifyOrEstablishConnection( + [](CHIP_ERROR err, CastingPlayer * playerPtr) { + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::verifyOrEstablishConnection() ConnectCallback called"); + if (err == CHIP_NO_ERROR) + { + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI:: Connected to Casting Player with device ID: %s", + playerPtr->GetId()); + MatterCastingPlayerJNIMgr().mConnectionSuccessHandler.Handle(nullptr); + } + else + { + ChipLogError(AppServer, "MatterCastingPlayer-JNI:: ConnectCallback, connection error: %" CHIP_ERROR_FORMAT, + err.Format()); + MatterCastingPlayerJNIMgr().mConnectionFailureHandler.Handle(err); + } + }, + static_cast(commissioningWindowTimeoutSec), desiredEndpointFilter); + return support::convertMatterErrorFromCppToJava(CHIP_NO_ERROR); +} + +JNI_METHOD(void, disconnect) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::disconnect()"); + + core::CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz); + VerifyOrReturn(castingPlayer != nullptr, + ChipLogError(AppServer, "MatterCastingPlayer-JNI::disconnect() castingPlayer == nullptr")); + + castingPlayer->Disconnect(); +} + +JNI_METHOD(jobject, getEndpoints) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterCastingPlayer-JNI::getEndpoints() called"); + + CastingPlayer * castingPlayer = support::convertCastingPlayerFromJavaToCpp(thiz); + VerifyOrReturnValue(castingPlayer != nullptr, nullptr, + ChipLogError(AppServer, "MatterCastingPlayer-JNI::getEndpoints() castingPlayer == nullptr")); + + const std::vector> endpoints = castingPlayer->GetEndpoints(); + jobject jEndpointList = nullptr; + chip::JniReferences::GetInstance().CreateArrayList(jEndpointList); + for (memory::Strong endpoint : endpoints) + { + jobject matterEndpointJavaObject = support::convertEndpointFromCppToJava(endpoint); + VerifyOrReturnValue(matterEndpointJavaObject != nullptr, jEndpointList, + ChipLogError(AppServer, "MatterCastingPlayer-JNI::getEndpoints(): Could not create Endpoint jobject")); + chip::JniReferences::GetInstance().AddToList(jEndpointList, matterEndpointJavaObject); + } + return jEndpointList; +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h new file mode 100644 index 00000000000000..7f58162fff52b3 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterCastingPlayer-JNI.h @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2020-2024 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. + */ + +#pragma once + +#include "../support/MatterCallback-JNI.h" +#include "core/CastingPlayer.h" // from tv-casting-common + +#include + +namespace matter { +namespace casting { +namespace core { + +class MatterCastingPlayerJNI +{ +public: + MatterCastingPlayerJNI() : mConnectionSuccessHandler([](void *) { return nullptr; }) {} + support::MatterCallbackJNI mConnectionSuccessHandler; + support::MatterFailureCallbackJNI mConnectionFailureHandler; + +private: + friend MatterCastingPlayerJNI & MatterCastingPlayerJNIMgr(); + static MatterCastingPlayerJNI sInstance; +}; + +inline class MatterCastingPlayerJNI & MatterCastingPlayerJNIMgr() +{ + return MatterCastingPlayerJNI::sInstance; +} +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp new file mode 100644 index 00000000000000..2e28b873599a9a --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020-2024 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. + * + */ + +#include "MatterEndpoint-JNI.h" + +#include "../JNIDACProvider.h" +#include "../support/Converters-JNI.h" +#include "../support/MatterCallback-JNI.h" +#include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" +#include "clusters/Clusters.h" // from tv-casting-common +#include "core/CastingApp.h" // from tv-casting-common +#include "core/CastingPlayer.h" // from tv-casting-common +#include "core/CastingPlayerDiscovery.h" // from tv-casting-common +#include "core/Endpoint.h" // from tv-casting-common + +#include +#include +#include +#include +#include + +using namespace chip; + +#define JNI_METHOD(RETURN, METHOD_NAME) \ + extern "C" JNIEXPORT RETURN JNICALL Java_com_matter_casting_core_MatterEndpoint_##METHOD_NAME + +namespace matter { +namespace casting { +namespace core { + +MatterEndpointJNI MatterEndpointJNI::sInstance; + +JNI_METHOD(jint, getId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getId() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturnValue(endpoint != nullptr, -1, ChipLogError(AppServer, "MatterEndpoint-JNI::getId() endpoint == nullptr")); + return endpoint->GetId(); +} + +JNI_METHOD(jint, getProductId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getProductId() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturnValue(endpoint != nullptr, -1, ChipLogError(AppServer, "MatterEndpoint-JNI::getProductId() endpoint == nullptr")); + return endpoint->GetProductId(); +} + +JNI_METHOD(jint, getVendorId) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getVendorId() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturnValue(endpoint != nullptr, -1, ChipLogError(AppServer, "MatterEndpoint-JNI::getVendorId() endpoint == nullptr")); + return endpoint->GetVendorId(); +} + +JNI_METHOD(jobject, getCastingPlayer) +(JNIEnv * env, jobject thiz) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(AppServer, "MatterEndpoint-JNI::getCastingPlayer() called"); + Endpoint * endpoint = support::convertEndpointFromJavaToCpp(thiz); + VerifyOrReturnValue(endpoint != nullptr, nullptr, + ChipLogError(AppServer, "MatterEndpoint-JNI::getCastingPlayer() endpoint == nullptr")); + return support::convertCastingPlayerFromCppToJava(std::shared_ptr(endpoint->GetCastingPlayer())); +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h similarity index 66% rename from examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h rename to examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h index 2870866895c868..f9534435ab1903 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingPlayer-JNI.h +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/MatterEndpoint-JNI.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2024 Project CHIP Authors + * Copyright (c) 2020-24 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,23 +18,26 @@ #pragma once +#include "core/Endpoint.h" // from tv-casting-common + #include +#include +#include namespace matter { namespace casting { namespace core { -class CastingPlayerJNI +class MatterEndpointJNI { -public: private: - friend CastingPlayerJNI & CastingAppJNIMgr(); - static CastingPlayerJNI sInstance; + friend MatterEndpointJNI & MatterEndpointJNIMgr(); + static MatterEndpointJNI sInstance; }; -inline class CastingPlayerJNI & CastingAppJNIMgr() +inline class MatterEndpointJNI & MatterEndpointJNIMgr() { - return CastingPlayerJNI::sInstance; + return MatterEndpointJNI::sInstance; } }; // namespace core }; // namespace casting diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp deleted file mode 100644 index 72c3677f357707..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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. - * - */ - -#include "CastingPlayerConverter-JNI.h" -#include - -namespace matter { -namespace casting { -namespace support { - -using namespace chip; - -jobject createJCastingPlayer(matter::casting::memory::Strong player) -{ - ChipLogProgress(AppServer, "CastingPlayerConverter-JNI.createJCastingPlayer() called"); - JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); - - // Get a reference to the MatterCastingPlayer Java class - jclass matterCastingPlayerJavaClass = env->FindClass("com/matter/casting/core/MatterCastingPlayer"); - if (matterCastingPlayerJavaClass == nullptr) - { - ChipLogError(AppServer, - "CastingPlayerConverter-JNI.createJCastingPlayer() could not locate MatterCastingPlayer Java class"); - return nullptr; - } - - // Get the constructor for the com/matter/casting/core/MatterCastingPlayer Java class - jmethodID constructor = - env->GetMethodID(matterCastingPlayerJavaClass, "", - "(ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;IIIJ)V"); - if (constructor == nullptr) - { - ChipLogError( - AppServer, - "CastingPlayerConverter-JNI.createJCastingPlayer() could not locate MatterCastingPlayer Java class constructor"); - return nullptr; - } - - // Convert the CastingPlayer fields to MatterCastingPlayer Java types - jobject jIpAddressList = nullptr; - const chip::Inet::IPAddress * ipAddresses = player->GetIPAddresses(); - if (ipAddresses != nullptr) - { - chip::JniReferences::GetInstance().CreateArrayList(jIpAddressList); - for (size_t i = 0; i < player->GetNumIPs() && i < chip::Dnssd::CommonResolutionData::kMaxIPAddresses; i++) - { - char addrCString[chip::Inet::IPAddress::kMaxStringLength]; - ipAddresses[i].ToString(addrCString, chip::Inet::IPAddress::kMaxStringLength); - jstring jIPAddressStr = env->NewStringUTF(addrCString); - - jclass jIPAddressClass = env->FindClass("java/net/InetAddress"); - jmethodID jGetByNameMid = - env->GetStaticMethodID(jIPAddressClass, "getByName", "(Ljava/lang/String;)Ljava/net/InetAddress;"); - jobject jIPAddress = env->CallStaticObjectMethod(jIPAddressClass, jGetByNameMid, jIPAddressStr); - - chip::JniReferences::GetInstance().AddToList(jIpAddressList, jIPAddress); - } - } - - // Create a new instance of the MatterCastingPlayer Java class - jobject jMatterCastingPlayer = nullptr; - jMatterCastingPlayer = env->NewObject(matterCastingPlayerJavaClass, constructor, static_cast(player->IsConnected()), - env->NewStringUTF(player->GetId()), env->NewStringUTF(player->GetHostName()), - env->NewStringUTF(player->GetDeviceName()), env->NewStringUTF(player->GetInstanceName()), - jIpAddressList, (jint) (player->GetPort()), (jint) (player->GetProductId()), - (jint) (player->GetVendorId()), (jlong) (player->GetDeviceType())); - if (jMatterCastingPlayer == nullptr) - { - ChipLogError(AppServer, - "CastingPlayerConverter-JNI.createJCastingPlayer() Warning: Could not create MatterCastingPlayer Java object"); - return jMatterCastingPlayer; - } - // Set the value of the _cppCastingPlayer field in the Java object to the C++ CastingPlayer pointer. - jfieldID longFieldId = env->GetFieldID(matterCastingPlayerJavaClass, "_cppCastingPlayer", "J"); - env->SetLongField(jMatterCastingPlayer, longFieldId, reinterpret_cast(player.get())); - return jMatterCastingPlayer; -} - -}; // namespace support -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.h deleted file mode 100644 index 91b0ac6c79b92f..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/CastingPlayerConverter-JNI.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * - * Copyright (c) 2023-2024 Project CHIP Authors - * - * 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. - */ -#pragma once - -#include "core/CastingPlayer.h" - -#include - -#include - -namespace matter { -namespace casting { -namespace support { - -/** - * @brief Convertes a native CastingPlayer into a MatterCastingPlayer jobject - * - * @param CastingPlayer represents a Matter commissioner that is able to play media to a physical - * output or to a display screen which is part of the device. - * - * @return pointer to the CastingPlayer jobject if created successfully, nullptr otherwise. - */ -jobject createJCastingPlayer(matter::casting::memory::Strong player); - -}; // namespace support -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp new file mode 100644 index 00000000000000..9798f2b48b9359 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2020-24 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. + * + */ + +#include "Converters-JNI.h" +#include + +namespace matter { +namespace casting { +namespace support { + +using namespace chip; + +jobject convertMatterErrorFromCppToJava(CHIP_ERROR inErr) +{ + ChipLogProgress(AppServer, "convertMatterErrorFromCppToJava() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass jMatterErrorClass; + CHIP_ERROR err = + chip::JniReferences::GetInstance().GetLocalClassRef(env, "com/matter/casting/support/MatterError", jMatterErrorClass); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr); + + jmethodID jMatterErrorConstructor = env->GetMethodID(jMatterErrorClass, "", "(JLjava/lang/String;)V"); + if (jMatterErrorConstructor == nullptr) + { + ChipLogError(AppServer, "Failed to access MatterError constructor"); + env->ExceptionClear(); + return nullptr; + } + + return env->NewObject(jMatterErrorClass, jMatterErrorConstructor, inErr.AsInteger(), nullptr); +} + +jobject convertEndpointFromCppToJava(matter::casting::memory::Strong endpoint) +{ + ChipLogProgress(AppServer, "convertEndpointFromCppToJava() called"); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + // Get a reference to the MatterEndpoint Java class + jclass matterEndpointJavaClass = env->FindClass("com/matter/casting/core/MatterEndpoint"); + if (matterEndpointJavaClass == nullptr) + { + ChipLogError(AppServer, "convertEndpointFromCppToJava() could not locate MatterEndpoint Java class"); + env->ExceptionClear(); + return nullptr; + } + + // Get the constructor for the com/matter/casting/core/MatterEndpoint Java class + jmethodID constructor = env->GetMethodID(matterEndpointJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "convertEndpointFromCppToJava() could not locate MatterEndpoint Java class constructor"); + env->ExceptionClear(); + return nullptr; + } + + // Create a new instance of the MatterEndpoint Java class + jobject jMatterEndpoint = nullptr; + jMatterEndpoint = env->NewObject(matterEndpointJavaClass, constructor); + if (jMatterEndpoint == nullptr) + { + ChipLogError(AppServer, "convertEndpointFromCppToJava(): Could not create MatterEndpoint Java object"); + return jMatterEndpoint; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(matterEndpointJavaClass, "_cppEndpoint", "J"); + env->SetLongField(jMatterEndpoint, longFieldId, reinterpret_cast(endpoint.get())); + return jMatterEndpoint; +} + +/** + * @brief Get the matter::casting::core::Endpoint object from the jobject jEndpointObject + */ +core::Endpoint * convertEndpointFromJavaToCpp(jobject jEndpointObject) +{ + ChipLogProgress(AppServer, "convertEndpointFromJavaToCpp() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass endpointClass = env->GetObjectClass(jEndpointObject); + if (endpointClass == nullptr) + { + ChipLogError(AppServer, "convertEndpointFromJavaToCpp() could not locate Endpoint Java class"); + env->ExceptionClear(); + return nullptr; + } + + jfieldID _cppEndpointFieldId = env->GetFieldID(endpointClass, "_cppEndpoint", "J"); + VerifyOrReturnValue(_cppEndpointFieldId != nullptr, nullptr, + ChipLogError(AppServer, "convertEndpointFromJavaToCpp _cppEndpointFieldId == nullptr")); + jlong _cppEndpointValue = env->GetLongField(jEndpointObject, _cppEndpointFieldId); + return reinterpret_cast(_cppEndpointValue); +} + +jobject convertCastingPlayerFromCppToJava(matter::casting::memory::Strong player) +{ + ChipLogProgress(AppServer, "convertCastingPlayerFromCppToJava() called"); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + // Get a reference to the MatterCastingPlayer Java class + jclass matterCastingPlayerJavaClass = env->FindClass("com/matter/casting/core/MatterCastingPlayer"); + if (matterCastingPlayerJavaClass == nullptr) + { + ChipLogError(AppServer, "convertCastingPlayerFromCppToJava() could not locate MatterCastingPlayer Java class"); + env->ExceptionClear(); + return nullptr; + } + + // Get the constructor for the com/matter/casting/core/MatterCastingPlayer Java class + jmethodID constructor = + env->GetMethodID(matterCastingPlayerJavaClass, "", + "(ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;IIIJ)V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "convertCastingPlayerFromCppToJava() could not locate MatterCastingPlayer Java class constructor"); + env->ExceptionClear(); + return nullptr; + } + + // Convert the CastingPlayer fields to MatterCastingPlayer Java types + jobject jIpAddressList = nullptr; + const chip::Inet::IPAddress * ipAddresses = player->GetIPAddresses(); + if (ipAddresses != nullptr) + { + chip::JniReferences::GetInstance().CreateArrayList(jIpAddressList); + for (size_t i = 0; i < player->GetNumIPs() && i < chip::Dnssd::CommonResolutionData::kMaxIPAddresses; i++) + { + char addrCString[chip::Inet::IPAddress::kMaxStringLength]; + ipAddresses[i].ToString(addrCString, chip::Inet::IPAddress::kMaxStringLength); + jstring jIPAddressStr = env->NewStringUTF(addrCString); + + jclass jIPAddressClass = env->FindClass("java/net/InetAddress"); + jmethodID jGetByNameMid = + env->GetStaticMethodID(jIPAddressClass, "getByName", "(Ljava/lang/String;)Ljava/net/InetAddress;"); + jobject jIPAddress = env->CallStaticObjectMethod(jIPAddressClass, jGetByNameMid, jIPAddressStr); + + chip::JniReferences::GetInstance().AddToList(jIpAddressList, jIPAddress); + } + } + + // Create a new instance of the MatterCastingPlayer Java class + jobject jMatterCastingPlayer = nullptr; + jMatterCastingPlayer = env->NewObject(matterCastingPlayerJavaClass, constructor, static_cast(player->IsConnected()), + env->NewStringUTF(player->GetId()), env->NewStringUTF(player->GetHostName()), + env->NewStringUTF(player->GetDeviceName()), env->NewStringUTF(player->GetInstanceName()), + jIpAddressList, (jint) (player->GetPort()), (jint) (player->GetProductId()), + (jint) (player->GetVendorId()), (jlong) (player->GetDeviceType())); + if (jMatterCastingPlayer == nullptr) + { + ChipLogError(AppServer, "convertCastingPlayerFromCppToJava(): Could not create MatterCastingPlayer Java object"); + env->ExceptionClear(); + return jMatterCastingPlayer; + } + // Set the value of the _cppCastingPlayer field in the Java object to the C++ CastingPlayer pointer. + jfieldID longFieldId = env->GetFieldID(matterCastingPlayerJavaClass, "_cppCastingPlayer", "J"); + env->SetLongField(jMatterCastingPlayer, longFieldId, reinterpret_cast(player.get())); + return jMatterCastingPlayer; +} + +core::CastingPlayer * convertCastingPlayerFromJavaToCpp(jobject jCastingPlayerObject) +{ + ChipLogProgress(AppServer, "convertCastingPlayerFromJavaToCpp() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass castingPlayerClass = env->GetObjectClass(jCastingPlayerObject); + if (castingPlayerClass == nullptr) + { + ChipLogError(AppServer, "convertCastingPlayerFromJavaToCpp() could not locate CastingPlayer Java class"); + env->ExceptionClear(); + return nullptr; + } + + jfieldID _cppCastingPlayerFieldId = env->GetFieldID(castingPlayerClass, "_cppCastingPlayer", "J"); + VerifyOrReturnValue(_cppCastingPlayerFieldId != nullptr, nullptr, + ChipLogError(AppServer, "convertCastingPlayerFromJavaToCpp _cppCastingPlayerFieldId == nullptr")); + jlong _cppCastingPlayerValue = env->GetLongField(jCastingPlayerObject, _cppCastingPlayerFieldId); + return reinterpret_cast(_cppCastingPlayerValue); +} + +jobject convertClusterFromCppToJava(matter::casting::memory::Strong cluster, const char * className) +{ + ChipLogProgress(AppServer, "convertClusterFromCppToJava() called"); + VerifyOrReturnValue(cluster.get() != nullptr, nullptr, + ChipLogError(AppServer, "convertClusterFromCppToJava() cluster.get() == nullptr")); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + // Get a reference to the cluster's Java class + jclass clusterJavaClass = env->FindClass(className); + if (clusterJavaClass == nullptr) + { + ChipLogError(AppServer, "convertClusterFromCppToJava could not locate cluster's Java class"); + env->ExceptionClear(); + return nullptr; + } + + // Get the constructor for the cluster's Java class + jmethodID constructor = env->GetMethodID(clusterJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "convertClusterFromCppToJava could not locate cluster's Java class constructor"); + env->ExceptionClear(); + return nullptr; + } + + // Create a new instance of the cluster's Java class + jobject jMatterCluster = nullptr; + jMatterCluster = env->NewObject(clusterJavaClass, constructor); + if (jMatterCluster == nullptr) + { + ChipLogError(AppServer, "convertClusterFromCppToJava: Could not create cluster's Java object"); + return jMatterCluster; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(clusterJavaClass, "_cppCluster", "J"); + env->SetLongField(jMatterCluster, longFieldId, reinterpret_cast(cluster.get())); + return jMatterCluster; +} + +/** + * @brief Get the matter::casting::core::Cluster object from the jobject jClusterObject + */ +core::BaseCluster * convertClusterFromJavaToCpp(jobject jClusterObject) +{ + ChipLogProgress(AppServer, "convertClusterFromJavaToCpp() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass clusterClass = env->GetObjectClass(jClusterObject); + if (clusterClass == nullptr) + { + ChipLogError(AppServer, "convertClusterFromJavaToCpp could not locate cluster's Java class"); + env->ExceptionClear(); + return nullptr; + } + + jfieldID _cppClusterFieldId = env->GetFieldID(clusterClass, "_cppCluster", "J"); + VerifyOrReturnValue(_cppClusterFieldId != nullptr, nullptr, + ChipLogError(AppServer, "convertClusterFromJavaToCpp() _cppCluster == nullptr")); + jlong _cppClusterValue = env->GetLongField(jClusterObject, _cppClusterFieldId); + return reinterpret_cast(_cppClusterValue); +} + +jobject convertCommandFromCppToJava(void * command, const char * className) +{ + ChipLogProgress(AppServer, "convertCommandFromCppToJava() called"); + VerifyOrReturnValue(command != nullptr, nullptr, ChipLogError(AppServer, "convertCommandFromCppToJava() command == nullptr")); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + // Get a reference to the command's Java class + jclass commandJavaClass = env->FindClass(className); + if (commandJavaClass == nullptr) + { + ChipLogError(AppServer, "convertCommandFromCppToJava() could not locate command's Java class"); + env->ExceptionClear(); + return nullptr; + } + + // Get the constructor for the command's Java class + jmethodID constructor = env->GetMethodID(commandJavaClass, "", "()V"); + if (constructor == nullptr) + { + ChipLogError(AppServer, "convertCommandFromCppToJava() could not locate command's Java class constructor"); + env->ExceptionClear(); + return nullptr; + } + + // Create a new instance of the command's Java class + jobject jMatterCommand = env->NewObject(commandJavaClass, constructor); + if (jMatterCommand == nullptr) + { + ChipLogError(AppServer, "convertCommandFromCppToJava(): Could not create command's Java object"); + env->ExceptionClear(); + return jMatterCommand; + } + // Set the value of the _cppEndpoint field in the Java object to the C++ Endpoint pointer. + jfieldID longFieldId = env->GetFieldID(commandJavaClass, "_cppCommand", "J"); + env->SetLongField(jMatterCommand, longFieldId, reinterpret_cast(command)); + return jMatterCommand; +} + +/** + * @brief Get the matter::casting::core::Command object from the jobject jCommandObject + */ +void * convertCommandFromJavaToCpp(jobject jCommandObject) +{ + ChipLogProgress(AppServer, "convertCommandFromJavaToCpp() called"); + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass commandClass = env->GetObjectClass(jCommandObject); + if (commandClass == nullptr) + { + ChipLogError(AppServer, "convertCommandFromJavaToCpp() could not locate command's Java class"); + env->ExceptionClear(); + return nullptr; + } + + jfieldID _cppCommandFieldId = env->GetFieldID(commandClass, "_cppCommand", "J"); + VerifyOrReturnValue(_cppCommandFieldId != nullptr, nullptr, + ChipLogError(AppServer, "convertCommandFromJavaToCpp() _cppCommand == nullptr")); + jlong _cppCommandValue = env->GetLongField(jCommandObject, _cppCommandFieldId); + return reinterpret_cast(_cppCommandValue); +} + +jobject convertLongFromCppToJava(uint64_t responseData) +{ + ChipLogProgress(AppServer, "convertLongFromCppToJava() called"); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(AppServer, "Could not get JNIEnv for current thread")); + + jclass responseTypeClass = env->FindClass("java/lang/Long"); + if (responseTypeClass == nullptr) + { + ChipLogError(AppServer, "convertLongFromCppToJava: Class for Response Type not found!"); + env->ExceptionClear(); + return nullptr; + } + + jmethodID constructor = env->GetMethodID(responseTypeClass, "", "(J)V"); + return env->NewObject(responseTypeClass, constructor, responseData); +} + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h new file mode 100644 index 00000000000000..ecc3a95d15bd74 --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/Converters-JNI.h @@ -0,0 +1,77 @@ +/* + * + * Copyright (c) 2020-24 Project CHIP Authors + * + * 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. + */ +#pragma once + +#include "core/BaseCluster.h" +#include "core/CastingPlayer.h" +#include "core/Command.h" +#include "core/Endpoint.h" + +#include + +#include + +namespace matter { +namespace casting { +namespace support { + +jobject convertMatterErrorFromCppToJava(CHIP_ERROR inErr); + +/** + * @brief Converts a native Endpoint into a MatterEndpoint jobject + * + * @return pointer to the Endpoint jobject if created successfully, nullptr otherwise. + */ +jobject convertEndpointFromCppToJava(matter::casting::memory::Strong endpoint); + +core::Endpoint * convertEndpointFromJavaToCpp(jobject jEndpointObject); + +/** + * @brief Convertes a native CastingPlayer into a MatterCastingPlayer jobject + * + * @param CastingPlayer represents a Matter commissioner that is able to play media to a physical + * output or to a display screen which is part of the device. + * + * @return pointer to the CastingPlayer jobject if created successfully, nullptr otherwise. + */ +jobject convertCastingPlayerFromCppToJava(matter::casting::memory::Strong player); + +core::CastingPlayer * convertCastingPlayerFromJavaToCpp(jobject jCastingPlayerObject); + +/** + * @brief Converts a native Cluster into a MatterCluster jobject + * + * @return pointer to the Cluster jobject if created successfully, nullptr otherwise. + */ +jobject convertClusterFromCppToJava(matter::casting::memory::Strong cluster, const char * className); + +core::BaseCluster * convertClusterFromJavaToCpp(jobject jClusterObject); + +/** + * @brief Converts a native Command into a MatterCommand jobject + * + * @return pointer to the Command jobject if created successfully, nullptr otherwise. + */ +jobject convertCommandFromCppToJava(void * command, const char * className); + +void * convertCommandFromJavaToCpp(jobject jCommandObject); + +jobject convertLongFromCppToJava(uint64_t responseData); + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.cpp deleted file mode 100644 index 1dce6f19d74776..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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. - * - */ - -#include "ErrorConverter-JNI.h" -#include - -namespace matter { -namespace casting { -namespace support { - -using namespace chip; - -jobject createJMatterError(CHIP_ERROR inErr) -{ - JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); - jclass jMatterErrorClass; - CHIP_ERROR err = - chip::JniReferences::GetInstance().GetLocalClassRef(env, "com/matter/casting/support/MatterError", jMatterErrorClass); - VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr); - - jmethodID jMatterErrorConstructor = env->GetMethodID(jMatterErrorClass, "", "(JLjava/lang/String;)V"); - - return env->NewObject(jMatterErrorClass, jMatterErrorConstructor, inErr.AsInteger(), nullptr); -} - -}; // namespace support -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.h deleted file mode 100644 index e11523397db4f3..00000000000000 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/ErrorConverter-JNI.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * - * Copyright (c) 2023 Project CHIP Authors - * - * 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. - */ -#pragma once - -#include - -#include - -namespace matter { -namespace casting { -namespace support { - -jobject createJMatterError(CHIP_ERROR inErr); - -}; // namespace support -}; // namespace casting -}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h new file mode 100644 index 00000000000000..3c56c426359d5c --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/MatterCallback-JNI.h @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2020-24 Project CHIP Authors + * + * 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. + */ +#pragma once + +#include "Converters-JNI.h" + +#include +#include +#include + +namespace matter { +namespace casting { +namespace support { + +template +class MatterCallbackJNI +{ +public: + MatterCallbackJNI(std::function conversionFn) { mConversionFn = conversionFn; } + + MatterCallbackJNI(const char * methodSignature, std::function conversionFn) + { + mMethodSignature = methodSignature; + mConversionFn = conversionFn; + } + + CHIP_ERROR SetUp(JNIEnv * env, jobject inCallback) + { + ChipLogProgress(AppServer, "MatterCallbackJNI::SetUp called"); + VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV, ChipLogError(AppServer, "JNIEnv was null!")); + + ReturnErrorOnFailure(mCallbackObject.Init(inCallback)); + + jclass mClazz = env->GetObjectClass(mCallbackObject.ObjectRef()); + VerifyOrReturnError(mClazz != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND, + ChipLogError(AppServer, "Failed to get callback Java class")); + + jclass mSuperClazz = env->GetSuperclass(mClazz); + VerifyOrReturnError(mSuperClazz != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND, + ChipLogError(AppServer, "Failed to get callback's parent's Java class")); + + mMethod = env->GetMethodID(mClazz, "handleInternal", mMethodSignature); + VerifyOrReturnError( + mMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND, + ChipLogError(AppServer, "Failed to access 'handleInternal' method with signature %s", mMethodSignature)); + + return CHIP_NO_ERROR; + } + + void Handle(T responseData) + { + ChipLogProgress(AppServer, "MatterCallbackJNI::Handle called"); + + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(AppServer, "Failed to get JNIEnv")); + + jobject jResponseData = mConversionFn(responseData); + + chip::DeviceLayer::StackUnlock unlock; + VerifyOrReturn(mCallbackObject.HasValidObjectRef(), + ChipLogError(AppServer, "MatterCallbackJNI::Handle mCallbackObject has no valid ObjectRef")); + VerifyOrReturn(mMethod != nullptr, ChipLogError(AppServer, "MatterCallbackJNI::Handle mMethod is nullptr")); + env->CallVoidMethod(mCallbackObject.ObjectRef(), mMethod, jResponseData); + } + +protected: + chip::JniGlobalReference mCallbackObject; + jmethodID mMethod = nullptr; + const char * mMethodSignature = "(Ljava/lang/Object;)V"; + std::function mConversionFn = nullptr; +}; + +class MatterFailureCallbackJNI : public MatterCallbackJNI +{ +public: + MatterFailureCallbackJNI() : MatterCallbackJNI(matter::casting::support::convertMatterErrorFromCppToJava) {} +}; + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/RotatingDeviceIdUniqueIdProvider-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/RotatingDeviceIdUniqueIdProvider-JNI.cpp index f1d29cd063c450..2af0e1000d6aaf 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/RotatingDeviceIdUniqueIdProvider-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/support/RotatingDeviceIdUniqueIdProvider-JNI.cpp @@ -82,7 +82,7 @@ MutableByteSpan * RotatingDeviceIdUniqueIdProviderJNI::Get() ChipLogProgress(AppServer, "RotatingDeviceIdUniqueIdProviderJNI.Get() called"); mRotatingDeviceIdUniqueIdSpan = MutableByteSpan(mRotatingDeviceIdUniqueId); CHIP_ERROR err = GetJavaByteByMethod(mGetMethod, mRotatingDeviceIdUniqueIdSpan); - VerifyOrReturnValue(err != CHIP_NO_ERROR, nullptr, + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, ChipLogError(AppServer, "Error calling GetJavaByteByMethod %" CHIP_ERROR_FORMAT, err.Format())); return &mRotatingDeviceIdUniqueIdSpan; } diff --git a/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml new file mode 100644 index 00000000000000..ae61681a0bbf2f --- /dev/null +++ b/examples/tv-casting-app/android/App/app/src/main/res/layout/fragment_matter_action_selector.xml @@ -0,0 +1,39 @@ + + + + + + + +