diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index fbaf631aaf7733..393f8e1e65590c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -476,9 +476,16 @@ jobs: scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-lock-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-lock-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_DRLK_2_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_DeviceBasicComposition.py" --script-args "--storage-path admin_storage.json --manual-code 10054912339 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-lock-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-lock-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_DeviceConformance.py" --script-args "--storage-path admin_storage.json --manual-code 10054912339 --bool-arg ignore_in_progress:True allow_provisional:True --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto --tests test_TC_IDM_10_2"' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EEM_2_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EEM_2_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EEM_2_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EEM_2_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EEM_2_5.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EEVSE_2_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EEVSE_2_4.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EEVSE_2_5.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EPM_2_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-energy-management-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-energy-management-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json --enable-key 000102030405060708090a0b0c0d0e0f" --script "src/python_testing/TC_EPM_2_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_FAN_3_1.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_FAN_3_2.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --app out/linux-x64-all-clusters-ipv6only-no-ble-no-wifi-tsan-clang-test/chip-all-clusters-app --factoryreset --app-args "--discriminator 1234 --KVS kvs1 --trace-to json:out/trace_data/app-{SCRIPT_BASE_NAME}.json" --script "src/python_testing/TC_FAN_3_3.py" --script-args "--storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:out/trace_data/test-{SCRIPT_BASE_NAME}.json --trace-to perfetto:out/trace_data/test-{SCRIPT_BASE_NAME}.perfetto"' diff --git a/examples/all-clusters-app/all-clusters-common/src/electrical-power-measurement-stub.cpp b/examples/all-clusters-app/all-clusters-common/src/electrical-power-measurement-stub.cpp index bac73dd0441634..4bcea0e8ed9895 100644 --- a/examples/all-clusters-app/all-clusters-common/src/electrical-power-measurement-stub.cpp +++ b/examples/all-clusters-app/all-clusters-common/src/electrical-power-measurement-stub.cpp @@ -16,239 +16,25 @@ * limitations under the License. */ -#include +#include using namespace chip; using namespace chip::app::Clusters; using namespace chip::app::Clusters::ElectricalPowerMeasurement; -using namespace chip::app::Clusters::ElectricalPowerMeasurement::Structs; -namespace chip { -namespace app { -namespace Clusters { -namespace ElectricalPowerMeasurement { - -static MeasurementAccuracyRangeStruct::Type activeCurrentAccuracyRanges[] = { { .rangeMin = 500, .rangeMax = 1000 } }; - -class StubAccuracyIterator : public Delegate::AccuracyIterator -{ -public: - size_t Count() override; - bool Next(MeasurementAccuracyStruct::Type & output) override; - void Release() override; - -private: - uint8_t mIndex; -}; - -size_t StubAccuracyIterator::Count() -{ - return 1; -} - -bool StubAccuracyIterator::Next(MeasurementAccuracyStruct::Type & output) -{ - if (mIndex >= 1) - { - return false; - } - output.measurementType = MeasurementTypeEnum::kActiveCurrent; - output.measured = true; - output.minMeasuredValue = -10000000; - output.maxMeasuredValue = 10000000; - output.accuracyRanges = DataModel::List(activeCurrentAccuracyRanges); - mIndex++; - return true; -} - -void StubAccuracyIterator::Release() -{ - mIndex = 0; -} - -class StubRangeIterator : public Delegate::RangeIterator -{ -public: - size_t Count() override; - bool Next(MeasurementRangeStruct::Type & output) override; - void Release() override; -}; - -size_t StubRangeIterator::Count() -{ - return 0; -} - -bool StubRangeIterator::Next(MeasurementRangeStruct::Type & output) -{ - return false; -} - -void StubRangeIterator::Release() {} - -class StubHarmonicMeasurementIterator : public Delegate::HarmonicMeasurementIterator -{ -public: - size_t Count() override; - bool Next(HarmonicMeasurementStruct::Type & output) override; - void Release() override; -}; - -size_t StubHarmonicMeasurementIterator::Count() -{ - return 0; -} - -bool StubHarmonicMeasurementIterator::Next(HarmonicMeasurementStruct::Type & output) -{ - return false; -} - -void StubHarmonicMeasurementIterator::Release() {} - -static StubAccuracyIterator accuracyIterator; -static StubRangeIterator rangeIterator; -static StubHarmonicMeasurementIterator harmonicMeasurementIterator; - -class ElectricalPowerMeasurementDelegate : public Delegate -{ -public: - PowerModeEnum GetPowerMode() override; - uint8_t GetNumberOfMeasurementTypes() override; - AccuracyIterator * IterateAccuracy() override; - RangeIterator * IterateRanges() override; - DataModel::Nullable GetVoltage() override; - DataModel::Nullable GetActiveCurrent() override; - DataModel::Nullable GetReactiveCurrent() override; - DataModel::Nullable GetApparentCurrent() override; - DataModel::Nullable GetActivePower() override; - DataModel::Nullable GetReactivePower() override; - DataModel::Nullable GetApparentPower() override; - DataModel::Nullable GetRMSVoltage() override; - DataModel::Nullable GetRMSCurrent() override; - DataModel::Nullable GetRMSPower() override; - DataModel::Nullable GetFrequency() override; - HarmonicMeasurementIterator * IterateHarmonicCurrents() override; - HarmonicMeasurementIterator * IterateHarmonicPhases() override; - DataModel::Nullable GetPowerFactor() override; - DataModel::Nullable GetNeutralCurrent() override; - - ~ElectricalPowerMeasurementDelegate() = default; -}; - -PowerModeEnum ElectricalPowerMeasurementDelegate::GetPowerMode() -{ - return PowerModeEnum::kAc; -} - -uint8_t ElectricalPowerMeasurementDelegate::GetNumberOfMeasurementTypes() -{ - return 1; -} - -Delegate::AccuracyIterator * ElectricalPowerMeasurementDelegate::IterateAccuracy() -{ - return &accuracyIterator; -} - -Delegate::RangeIterator * ElectricalPowerMeasurementDelegate::IterateRanges() -{ - return &rangeIterator; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetVoltage() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetActiveCurrent() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetReactiveCurrent() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetApparentCurrent() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetActivePower() -{ - return DataModel::Nullable(10000); -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetReactivePower() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetApparentPower() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetRMSVoltage() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetRMSCurrent() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetRMSPower() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetFrequency() -{ - return {}; -} - -Delegate::HarmonicMeasurementIterator * ElectricalPowerMeasurementDelegate::IterateHarmonicCurrents() -{ - return &harmonicMeasurementIterator; -} - -Delegate::HarmonicMeasurementIterator * ElectricalPowerMeasurementDelegate::IterateHarmonicPhases() -{ - return &harmonicMeasurementIterator; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetPowerFactor() -{ - return {}; -} - -DataModel::Nullable ElectricalPowerMeasurementDelegate::GetNeutralCurrent() -{ - return {}; -} - -} // namespace ElectricalPowerMeasurement -} // namespace Clusters -} // namespace app -} // namespace chip - -static std::unique_ptr gDelegate; -static std::unique_ptr gInstance; +static std::unique_ptr gEPMDelegate; +static std::unique_ptr gEPMInstance; void emberAfElectricalPowerMeasurementClusterInitCallback(chip::EndpointId endpointId) { VerifyOrDie(endpointId == 1); // this cluster is only enabled for endpoint 1. - VerifyOrDie(!gInstance); + VerifyOrDie(!gEPMInstance); - gDelegate = std::make_unique(); - if (gDelegate) + gEPMDelegate = std::make_unique(); + if (gEPMDelegate) { - gInstance = std::make_unique( - endpointId, *gDelegate, + gEPMInstance = std::make_unique( + endpointId, *gEPMDelegate, BitMask(Feature::kDirectCurrent, Feature::kAlternatingCurrent, Feature::kPolyphasePower, Feature::kHarmonics, Feature::kPowerQuality), BitMask( @@ -260,6 +46,6 @@ void emberAfElectricalPowerMeasurementClusterInitCallback(chip::EndpointId endpo OptionalAttributes::kOptionalAttributeFrequency, OptionalAttributes::kOptionalAttributePowerFactor, OptionalAttributes::kOptionalAttributeNeutralCurrent)); - gInstance->Init(); + gEPMInstance->Init(); } } diff --git a/examples/all-clusters-app/ameba/chip_main.cmake b/examples/all-clusters-app/ameba/chip_main.cmake index 6cc76ad25e7dd8..0d8b99c117716e 100755 --- a/examples/all-clusters-app/ameba/chip_main.cmake +++ b/examples/all-clusters-app/ameba/chip_main.cmake @@ -179,6 +179,7 @@ list( ${chip_dir}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp ${chip_dir}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp + ${chip_dir}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp ${chip_dir}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp ${chip_dir}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp diff --git a/examples/all-clusters-app/asr/BUILD.gn b/examples/all-clusters-app/asr/BUILD.gn index 263bd8cf7734b7..6f70452fa71bbe 100755 --- a/examples/all-clusters-app/asr/BUILD.gn +++ b/examples/all-clusters-app/asr/BUILD.gn @@ -84,6 +84,7 @@ asr_executable("clusters_app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${examples_plat_dir}/ButtonHandler.cpp", diff --git a/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn b/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn index 6ec6202ea21209..155bdacc2a8e4b 100644 --- a/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn +++ b/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn @@ -89,6 +89,7 @@ ti_simplelink_executable("all-clusters-app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${chip_root}/examples/providers/DeviceInfoProviderImpl.cpp", diff --git a/examples/all-clusters-app/cc13x4_26x4/BUILD.gn b/examples/all-clusters-app/cc13x4_26x4/BUILD.gn index 02dbebf7e6d8c9..45d8d66d31c4b5 100644 --- a/examples/all-clusters-app/cc13x4_26x4/BUILD.gn +++ b/examples/all-clusters-app/cc13x4_26x4/BUILD.gn @@ -92,6 +92,7 @@ ti_simplelink_executable("all-clusters-app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${chip_root}/examples/providers/DeviceInfoProviderImpl.cpp", diff --git a/examples/all-clusters-app/infineon/psoc6/BUILD.gn b/examples/all-clusters-app/infineon/psoc6/BUILD.gn index f2b9ab939d6f38..9a0fbe3b3b37ac 100644 --- a/examples/all-clusters-app/infineon/psoc6/BUILD.gn +++ b/examples/all-clusters-app/infineon/psoc6/BUILD.gn @@ -120,6 +120,7 @@ psoc6_executable("clusters_app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${examples_plat_dir}/LEDWidget.cpp", diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index 799c9ab3f1f451..65d00a6e0fc914 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -50,6 +50,7 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/device-energy-management-mode.cpp", diff --git a/examples/all-clusters-app/mbed/CMakeLists.txt b/examples/all-clusters-app/mbed/CMakeLists.txt index f2b6d9f5e88d27..3d1a179a93bbb2 100644 --- a/examples/all-clusters-app/mbed/CMakeLists.txt +++ b/examples/all-clusters-app/mbed/CMakeLists.txt @@ -72,6 +72,7 @@ target_sources(${APP_TARGET} PRIVATE ${ALL_CLUSTERS_COMMON}/src/smco-stub.cpp ${ALL_CLUSTERS_COMMON}/src/static-supported-modes-manager.cpp ${ALL_CLUSTERS_COMMON}/src/static-supported-temperature-levels.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/ElectricalPowerMeasurementDelegate.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseManager.cpp ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementDelegateImpl.cpp diff --git a/examples/all-clusters-app/nrfconnect/CMakeLists.txt b/examples/all-clusters-app/nrfconnect/CMakeLists.txt index 49bf4fb7f95e3a..563295e059b46c 100644 --- a/examples/all-clusters-app/nrfconnect/CMakeLists.txt +++ b/examples/all-clusters-app/nrfconnect/CMakeLists.txt @@ -72,6 +72,7 @@ target_sources(app PRIVATE ${ALL_CLUSTERS_COMMON_DIR}/src/resource-monitoring-delegates.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementManager.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/ElectricalPowerMeasurementDelegate.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyEvseManager.cpp ${NRFCONNECT_COMMON}/util/LEDWidget.cpp) diff --git a/examples/all-clusters-app/nxp/mw320/BUILD.gn b/examples/all-clusters-app/nxp/mw320/BUILD.gn index a858c18e87fed2..65d2df0401dd62 100644 --- a/examples/all-clusters-app/nxp/mw320/BUILD.gn +++ b/examples/all-clusters-app/nxp/mw320/BUILD.gn @@ -88,6 +88,7 @@ mw320_executable("shell_mw320") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${chip_root}/src/lib/shell/streamer_mw320.cpp", diff --git a/examples/all-clusters-app/openiotsdk/CMakeLists.txt b/examples/all-clusters-app/openiotsdk/CMakeLists.txt index 3b50f286b1683b..6d2fb76c0db919 100644 --- a/examples/all-clusters-app/openiotsdk/CMakeLists.txt +++ b/examples/all-clusters-app/openiotsdk/CMakeLists.txt @@ -65,6 +65,7 @@ target_sources(${APP_TARGET} ${ALL_CLUSTERS_COMMON}/src/resource-monitoring-delegates.cpp ${ALL_CLUSTERS_COMMON}/src/static-supported-modes-manager.cpp ${ALL_CLUSTERS_COMMON}/src/binding-handler.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/ElectricalPowerMeasurementDelegate.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseManager.cpp ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementDelegateImpl.cpp diff --git a/examples/all-clusters-app/telink/CMakeLists.txt b/examples/all-clusters-app/telink/CMakeLists.txt index ae715a8c41ebfb..2c59e658a7e6dc 100644 --- a/examples/all-clusters-app/telink/CMakeLists.txt +++ b/examples/all-clusters-app/telink/CMakeLists.txt @@ -85,6 +85,7 @@ target_sources(app PRIVATE ${ALL_CLUSTERS_COMMON_DIR}/src/device-energy-management-stub.cpp ${ALL_CLUSTERS_COMMON_DIR}/src/energy-evse-stub.cpp ${ALL_CLUSTERS_COMMON_DIR}/src/resource-monitoring-delegates.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/ElectricalPowerMeasurementDelegate.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyEvseManager.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementDelegateImpl.cpp diff --git a/examples/all-clusters-app/tizen/BUILD.gn b/examples/all-clusters-app/tizen/BUILD.gn index 23a045a7532429..4a60be79bc1e49 100644 --- a/examples/all-clusters-app/tizen/BUILD.gn +++ b/examples/all-clusters-app/tizen/BUILD.gn @@ -39,6 +39,7 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", ] diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.matter b/examples/energy-management-app/energy-management-common/energy-management-app.matter index f593b52d3ab71e..1ab71d2cc0769d 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.matter +++ b/examples/energy-management-app/energy-management-common/energy-management-app.matter @@ -1893,6 +1893,9 @@ endpoint 1 { callback attribute powerMode; callback attribute numberOfMeasurementTypes; callback attribute accuracy; + callback attribute ranges; + callback attribute voltage; + callback attribute activeCurrent; callback attribute activePower; callback attribute generatedCommandList; callback attribute acceptedCommandList; @@ -1904,8 +1907,13 @@ endpoint 1 { server cluster ElectricalEnergyMeasurement { emits event CumulativeEnergyMeasured; + emits event PeriodicEnergyMeasured; callback attribute accuracy; callback attribute cumulativeEnergyImported; + callback attribute cumulativeEnergyExported; + callback attribute periodicEnergyImported; + callback attribute periodicEnergyExported; + callback attribute cumulativeEnergyReset; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.zap b/examples/energy-management-app/energy-management-common/energy-management-app.zap index ed18c81d0738e5..ba1fa29d585485 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.zap +++ b/examples/energy-management-app/energy-management-common/energy-management-app.zap @@ -1719,10 +1719,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, @@ -1735,10 +1735,10 @@ "side": "server", "type": "int16u", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2812,7 +2812,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2828,7 +2828,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2844,12 +2844,60 @@ "storageOption": "External", "singleton": 0, "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Ranges", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, "defaultValue": "", "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "Voltage", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "voltage_mv", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ActiveCurrent", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "amperage_ma", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "ActivePower", "code": 8, @@ -2860,7 +2908,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2876,7 +2924,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2892,7 +2940,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2908,7 +2956,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2924,7 +2972,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2940,7 +2988,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3005,6 +3053,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "CumulativeEnergyExported", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "EnergyMeasurementStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PeriodicEnergyImported", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "EnergyMeasurementStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PeriodicEnergyExported", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "EnergyMeasurementStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CumulativeEnergyReset", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "CumulativeEnergyResetStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "GeneratedCommandList", "code": 65528, @@ -3109,6 +3221,13 @@ "mfgCode": null, "side": "server", "included": 1 + }, + { + "name": "PeriodicEnergyMeasured", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 } ] }, diff --git a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h index f1c3eac6f60dc9..fcae38dcafd2b5 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h @@ -19,6 +19,7 @@ #pragma once #include +#include #include using chip::Protocols::InteractionModel::Status; @@ -34,13 +35,29 @@ namespace EnergyEvse { class EVSEManufacturer { public: - EVSEManufacturer(EnergyEvseManager * aInstance) { mInstance = aInstance; } - EnergyEvseManager * GetInstance() { return mInstance; } - EnergyEvseDelegate * GetDelegate() + EVSEManufacturer(EnergyEvseManager * aEvseInstance, + ElectricalPowerMeasurement::ElectricalPowerMeasurementInstance * aEPMInstance) { - if (mInstance) + mEvseInstance = aEvseInstance; + mEPMInstance = aEPMInstance; + } + EnergyEvseManager * GetEvseInstance() { return mEvseInstance; } + ElectricalPowerMeasurement::ElectricalPowerMeasurementInstance * GetEPMInstance() { return mEPMInstance; } + + EnergyEvseDelegate * GetEvseDelegate() + { + if (mEvseInstance) + { + return mEvseInstance->GetDelegate(); + } + return nullptr; + } + + ElectricalPowerMeasurement::ElectricalPowerMeasurementDelegate * GetEPMDelegate() + { + if (mEPMInstance) { - return mInstance->GetDelegate(); + return mEPMInstance->GetDelegate(); } return nullptr; } @@ -60,25 +77,42 @@ class EVSEManufacturer */ static void ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg); + /** + * @brief Allows a client application to initialise the Accuracy, Measurement types etc + */ + CHIP_ERROR InitializePowerMeasurementCluster(); + /** * @brief Allows a client application to send in power readings into the system * - * @param[in] aEndpointId - Endpoint to send to EPM Cluster - * @param[in] aActivePower_mW - Power measured in milli-watts - * @param[in] aVoltage_mV - Voltage measured in milli-volts - * @param[in] aCurrent_mA - Current measured in milli-amps + * @param[in] aEndpointId - Endpoint to send to EPM Cluster + * @param[in] aActivePower_mW - ActivePower measured in milli-watts + * @param[in] aVoltage_mV - Voltage measured in milli-volts + * @param[in] aActiveCurrent_mA - ActiveCurrent measured in milli-amps */ CHIP_ERROR SendPowerReading(EndpointId aEndpointId, int64_t aActivePower_mW, int64_t aVoltage_mV, int64_t aCurrent_mA); /** - * @brief Allows a client application to send in energy readings into the system + * @brief Allows a client application to send cumulative energy readings into the system * * This is a helper function to add timestamps to the readings * * @param[in] aCumulativeEnergyImported -total energy imported in milli-watthours * @param[in] aCumulativeEnergyExported -total energy exported in milli-watthours */ - CHIP_ERROR SendEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, int64_t aCumulativeEnergyExported); + CHIP_ERROR SendCumulativeEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, + int64_t aCumulativeEnergyExported); + + /** + * @brief Allows a client application to send periodic energy readings into the system + * + * This is a helper function to add timestamps to the readings + * + * @param[in] aPeriodicEnergyImported - energy imported in milli-watthours in last period + * @param[in] aPeriodicEnergyExported - energy exported in milli-watthours in last period + */ + CHIP_ERROR SendPeriodicEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, + int64_t aCumulativeEnergyExported); /** Fake Meter data generation - used for testing EPM/EEM clusters */ /** @@ -88,14 +122,20 @@ class EVSEManufacturer * @param[in] aPower_mW - the mean power of the load * Positive power indicates Imported energy (e.g. a load) * Negative power indicated Exported energy (e.g. a generator) - * @param[in] aPowerRandomness_mW This is used to scale random power fluctuations around the mean power of the load - * + * @param[in] aPowerRandomness_mW This is used to define the max randomness of the + * random power values around the mean power of the load + * @param[in] aVoltage_mV - the nominal voltage measurement + * @param[in] aVoltageRandomness_mV This is used to define the max randomness of the + * random voltage values + * @param[in] aCurrent_mA - the nominal current measurement + * @param[in] aCurrentRandomness_mA This is used to define the max randomness of the + * random current values * @param[in] aInterval_s - the callback interval in seconds * @param[in] bReset - boolean: true will reset the energy values to 0 */ - void StartFakeReadings(EndpointId aEndpointId, int64_t aPower_mW, uint32_t aPowerRandomness_mW, uint8_t aInterval_s, + void StartFakeReadings(EndpointId aEndpointId, int64_t aPower_mW, uint32_t aPowerRandomness_mW, int64_t aVoltage_mV, + uint32_t aVoltageRandomness_mV, int64_t aCurrent_mA, uint32_t aCurrentRandomness_mA, uint8_t aInterval_s, bool bReset); - /** * @brief Stops any active updates to the fake load data callbacks */ @@ -111,7 +151,8 @@ class EVSEManufacturer static void FakeReadingsTimerExpiry(System::Layer * systemLayer, void * manufacturer); private: - EnergyEvseManager * mInstance; + EnergyEvseManager * mEvseInstance; + ElectricalPowerMeasurement::ElectricalPowerMeasurementInstance * mEPMInstance; int64_t mLastChargingEnergyMeter = 0; int64_t mLastDischargingEnergyMeter = 0; diff --git a/examples/energy-management-app/energy-management-common/include/ElectricalPowerMeasurementDelegate.h b/examples/energy-management-app/energy-management-common/include/ElectricalPowerMeasurementDelegate.h new file mode 100644 index 00000000000000..d6fd4e72928203 --- /dev/null +++ b/examples/energy-management-app/energy-management-common/include/ElectricalPowerMeasurementDelegate.h @@ -0,0 +1,138 @@ +/* + * + * 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 { +namespace Clusters { +namespace ElectricalPowerMeasurement { + +class ElectricalPowerMeasurementDelegate : public ElectricalPowerMeasurement::Delegate +{ +public: + ~ElectricalPowerMeasurementDelegate() = default; + + static constexpr uint8_t kMaxNumberOfMeasurementTypes = 14; // From spec + static constexpr uint8_t kDefaultNumberOfMeasurementTypes = 1; + + // Attribute Accessors + PowerModeEnum GetPowerMode() override { return mPowerMode; } + uint8_t GetNumberOfMeasurementTypes() override; + + /* These functions are called by the ReadAttribute handler to iterate through lists + * The cluster server will call StartRead to allow the delegate to create a temporary + * lock on the data. + * The delegate is expected to not change these values once StartRead has been called + * until the EndRead() has been called (e.g. releasing a lock on the data) + */ + CHIP_ERROR StartAccuracyRead() override; + CHIP_ERROR GetAccuracyByIndex(uint8_t, Structs::MeasurementAccuracyStruct::Type &) override; + CHIP_ERROR EndAccuracyRead() override; + + CHIP_ERROR StartRangesRead() override; + CHIP_ERROR GetRangeByIndex(uint8_t, Structs::MeasurementRangeStruct::Type &) override; + CHIP_ERROR EndRangesRead() override; + + CHIP_ERROR StartHarmonicCurrentsRead() override; + CHIP_ERROR GetHarmonicCurrentsByIndex(uint8_t, Structs::HarmonicMeasurementStruct::Type &) override; + CHIP_ERROR EndHarmonicCurrentsRead() override; + + CHIP_ERROR StartHarmonicPhasesRead() override; + CHIP_ERROR GetHarmonicPhasesByIndex(uint8_t, Structs::HarmonicMeasurementStruct::Type &) override; + CHIP_ERROR EndHarmonicPhasesRead() override; + + DataModel::Nullable GetVoltage() override { return mVoltage; } + DataModel::Nullable GetActiveCurrent() override { return mActiveCurrent; } + DataModel::Nullable GetReactiveCurrent() override { return mReactiveCurrent; } + DataModel::Nullable GetApparentCurrent() override { return mApparentCurrent; } + DataModel::Nullable GetActivePower() override { return mActivePower; } + DataModel::Nullable GetReactivePower() override { return mReactivePower; } + DataModel::Nullable GetApparentPower() override { return mApparentPower; } + DataModel::Nullable GetRMSVoltage() override { return mRMSVoltage; } + DataModel::Nullable GetRMSCurrent() override { return mRMSCurrent; } + DataModel::Nullable GetRMSPower() override { return mRMSPower; } + DataModel::Nullable GetFrequency() override { return mFrequency; } + DataModel::Nullable GetPowerFactor() override { return mPowerFactor; } + DataModel::Nullable GetNeutralCurrent() override { return mNeutralCurrent; }; + + // Internal Application API to set attribute values + CHIP_ERROR SetPowerMode(PowerModeEnum); + CHIP_ERROR SetVoltage(DataModel::Nullable); + CHIP_ERROR SetActiveCurrent(DataModel::Nullable); + CHIP_ERROR SetReactiveCurrent(DataModel::Nullable); + CHIP_ERROR SetApparentCurrent(DataModel::Nullable); + CHIP_ERROR SetActivePower(DataModel::Nullable); + CHIP_ERROR SetReactivePower(DataModel::Nullable); + CHIP_ERROR SetApparentPower(DataModel::Nullable); + CHIP_ERROR SetRMSVoltage(DataModel::Nullable); + CHIP_ERROR SetRMSCurrent(DataModel::Nullable); + CHIP_ERROR SetRMSPower(DataModel::Nullable); + CHIP_ERROR SetFrequency(DataModel::Nullable); + CHIP_ERROR SetPowerFactor(DataModel::Nullable); + CHIP_ERROR SetNeutralCurrent(DataModel::Nullable); + +private: + // Attribute storage + PowerModeEnum mPowerMode; + DataModel::Nullable mVoltage; + DataModel::Nullable mActiveCurrent; + DataModel::Nullable mReactiveCurrent; + DataModel::Nullable mApparentCurrent; + DataModel::Nullable mActivePower; + DataModel::Nullable mReactivePower; + DataModel::Nullable mApparentPower; + DataModel::Nullable mRMSVoltage; + DataModel::Nullable mRMSCurrent; + DataModel::Nullable mRMSPower; + DataModel::Nullable mFrequency; + DataModel::Nullable mPowerFactor; + DataModel::Nullable mNeutralCurrent; +}; + +class ElectricalPowerMeasurementInstance : public Instance +{ +public: + ElectricalPowerMeasurementInstance(EndpointId aEndpointId, ElectricalPowerMeasurementDelegate & aDelegate, Feature aFeature, + OptionalAttributes aOptionalAttributes) : + ElectricalPowerMeasurement::Instance(aEndpointId, aDelegate, aFeature, aOptionalAttributes) + { + mDelegate = &aDelegate; + } + + // Delete copy constructor and assignment operator. + ElectricalPowerMeasurementInstance(const ElectricalPowerMeasurementInstance &) = delete; + ElectricalPowerMeasurementInstance(const ElectricalPowerMeasurementInstance &&) = delete; + ElectricalPowerMeasurementInstance & operator=(const ElectricalPowerMeasurementInstance &) = delete; + + CHIP_ERROR Init(); + void Shutdown(); + + ElectricalPowerMeasurementDelegate * GetDelegate() { return mDelegate; }; + +private: + ElectricalPowerMeasurementDelegate * mDelegate; +}; + +} // namespace ElectricalPowerMeasurement +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/energy-management-app/energy-management-common/src/EnergyManagementManager.cpp b/examples/energy-management-app/energy-management-common/include/EnergyEvseMain.h similarity index 83% rename from examples/energy-management-app/energy-management-common/src/EnergyManagementManager.cpp rename to examples/energy-management-app/energy-management-common/include/EnergyEvseMain.h index 884a5bcf5b65ce..2e7e29537ef3b4 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyManagementManager.cpp +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseMain.h @@ -1,7 +1,6 @@ /* * - * Copyright (c) 2023 Project CHIP Authors - * Copyright (c) 2019 Google LLC. + * Copyright (c) 2024 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,3 +15,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +#pragma once + +void EvseApplicationInit(); +void EvseApplicationShutdown(); diff --git a/examples/energy-management-app/energy-management-common/include/EnergyManagementManager.h b/examples/energy-management-app/energy-management-common/include/EnergyManagementManager.h deleted file mode 100644 index 3dac6f579c609e..00000000000000 --- a/examples/energy-management-app/energy-management-common/include/EnergyManagementManager.h +++ /dev/null @@ -1,26 +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. - */ - -#pragma once - -#include -#include - -#include - -#include diff --git a/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp index 28c6b8942ff493..c918d3a729d3a4 100644 --- a/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp @@ -18,8 +18,6 @@ #include "DeviceEnergyManagementDelegateImpl.h" -#include -#include #include using namespace chip; diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index 16d3a35b0a30b4..cb7a7a44991e16 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -21,11 +21,14 @@ #include #include #include +#include using namespace chip; using namespace chip::app; +using namespace chip::app::DataModel; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; +using namespace chip::app::Clusters::ElectricalPowerMeasurement; using namespace chip::app::Clusters::ElectricalEnergyMeasurement; using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Structs; @@ -34,7 +37,7 @@ CHIP_ERROR EVSEManufacturer::Init() /* Manufacturers should modify this to do any custom initialisation */ /* Register callbacks */ - EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + EnergyEvseDelegate * dg = GetEvseManufacturer()->GetEvseDelegate(); if (dg == nullptr) { ChipLogError(AppServer, "EVSE Delegate is not initialized"); @@ -43,6 +46,8 @@ CHIP_ERROR EVSEManufacturer::Init() dg->HwRegisterEvseCallbackHandler(ApplicationCallbackHandler, reinterpret_cast(this)); + ReturnErrorOnFailure(InitializePowerMeasurementCluster()); + /* * This is an example implementation for manufacturers to consider * @@ -66,12 +71,8 @@ CHIP_ERROR EVSEManufacturer::Init() * When the EV is plugged in, and asking for demand change the state * and set the CableAssembly current limit * - * EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); - * if (dg == nullptr) - * { - * ChipLogError(AppServer, "Delegate is not initialized"); - * return CHIP_ERROR_UNINITIALIZED; - * } + * EnergyEvseDelegate * dg = GetEvseManufacturer()->GetEvseDelegate(); + * VerifyOrReturnError(dg != nullptr, CHIP_ERROR_UNINITIALIZED); * * dg->HwSetState(StateEnum::kPluggedInDemand); * dg->HwSetCableAssemblyLimit(63000); // 63A = 63000mA @@ -93,68 +94,109 @@ CHIP_ERROR EVSEManufacturer::Shutdown() return CHIP_NO_ERROR; } +/** + * @brief Allows a client application to initialise the Accuracy, Measurement types etc + */ +CHIP_ERROR EVSEManufacturer::InitializePowerMeasurementCluster() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrReturnError(mn != nullptr, CHIP_ERROR_UNINITIALIZED); + + ElectricalPowerMeasurementDelegate * dg = mn->GetEPMDelegate(); + VerifyOrReturnError(dg != nullptr, CHIP_ERROR_UNINITIALIZED); + + ReturnErrorOnFailure(dg->SetPowerMode(PowerModeEnum::kAc)); + + return CHIP_NO_ERROR; +} + /** * @brief Allows a client application to send in power readings into the system * - * @param[in] aEndpointId - Endpoint to send to EPM Cluster - * @param[in] aActivePower_mW - Power measured in milli-watts - * @param[in] aVoltage_mV - Voltage measured in milli-volts - * @param[in] aCurrent_mA - Current measured in milli-amps + * @param[in] aEndpointId - Endpoint to send to EPM Cluster + * @param[in] aActivePower_mW - ActivePower measured in milli-watts + * @param[in] aVoltage_mV - Voltage measured in milli-volts + * @param[in] aActiveCurrent_mA - ActiveCurrent measured in milli-amps */ CHIP_ERROR EVSEManufacturer::SendPowerReading(EndpointId aEndpointId, int64_t aActivePower_mW, int64_t aVoltage_mV, - int64_t aCurrent_mA) + int64_t aActiveCurrent_mA) { - // TODO add Power Readings when EPM cluster is merged + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrReturnError(mn != nullptr, CHIP_ERROR_UNINITIALIZED); + + ElectricalPowerMeasurementDelegate * dg = mn->GetEPMDelegate(); + VerifyOrReturnError(dg != nullptr, CHIP_ERROR_UNINITIALIZED); + + dg->SetActivePower(MakeNullable(aActivePower_mW)); + dg->SetVoltage(MakeNullable(aVoltage_mV)); + dg->SetActiveCurrent(MakeNullable(aActiveCurrent_mA)); return CHIP_NO_ERROR; } /** - * @brief Allows a client application to send in energy readings into the system + * @brief Allows a client application to send cumulative energy readings into the system * * This is a helper function to add timestamps to the readings * * @param[in] aCumulativeEnergyImported -total energy imported in milli-watthours * @param[in] aCumulativeEnergyExported -total energy exported in milli-watthours */ -CHIP_ERROR EVSEManufacturer::SendEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, - int64_t aCumulativeEnergyExported) +CHIP_ERROR EVSEManufacturer::SendCumulativeEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, + int64_t aCumulativeEnergyExported) { MeasurementData * data = MeasurementDataForEndpoint(aEndpointId); + VerifyOrReturnError(data != nullptr, CHIP_ERROR_UNINITIALIZED); EnergyMeasurementStruct::Type energyImported; EnergyMeasurementStruct::Type energyExported; - // Get current timestamp - uint32_t currentTimestamp; - CHIP_ERROR err = GetEpochTS(currentTimestamp); - if (err != CHIP_NO_ERROR) - { - ChipLogError(AppServer, "GetEpochTS returned error getting timestamp"); - return err; - } - /** IMPORT */ // Copy last endTimestamp into new startTimestamp if it exists energyImported.startTimestamp.ClearValue(); + energyImported.startSystime.ClearValue(); if (data->cumulativeImported.HasValue()) { energyImported.startTimestamp = data->cumulativeImported.Value().endTimestamp; + energyImported.startSystime = data->cumulativeImported.Value().endSystime; } - energyImported.endTimestamp.SetValue(currentTimestamp); energyImported.energy = aCumulativeEnergyImported; /** EXPORT */ // Copy last endTimestamp into new startTimestamp if it exists energyExported.startTimestamp.ClearValue(); + energyExported.startSystime.ClearValue(); if (data->cumulativeExported.HasValue()) { energyExported.startTimestamp = data->cumulativeExported.Value().endTimestamp; + energyExported.startSystime = data->cumulativeExported.Value().endSystime; } - energyExported.endTimestamp.SetValue(currentTimestamp); + energyExported.energy = aCumulativeEnergyExported; + // Get current timestamp + uint32_t currentTimestamp; + CHIP_ERROR err = GetEpochTS(currentTimestamp); + if (err == CHIP_NO_ERROR) + { + // use EpochTS + energyImported.endTimestamp.SetValue(currentTimestamp); + energyExported.endTimestamp.SetValue(currentTimestamp); + } + else + { + ChipLogError(AppServer, "GetEpochTS returned error getting timestamp %" CHIP_ERROR_FORMAT, err.Format()); + + // use systemTime as a fallback + System::Clock::Milliseconds64 system_time_ms = + std::chrono::duration_cast(chip::Server::GetInstance().TimeSinceInit()); + uint64_t nowMS = static_cast(system_time_ms.count()); + + energyImported.endSystime.SetValue(nowMS); + energyExported.endSystime.SetValue(nowMS); + } + // call the SDK to update attributes and generate an event if (!NotifyCumulativeEnergyMeasured(aEndpointId, MakeOptional(energyImported), MakeOptional(energyExported))) { @@ -165,17 +207,97 @@ CHIP_ERROR EVSEManufacturer::SendEnergyReading(EndpointId aEndpointId, int64_t a return CHIP_NO_ERROR; } +/** + * @brief Allows a client application to send periodic energy readings into the system + * + * This is a helper function to add timestamps to the readings + * + * @param[in] aPeriodicEnergyImported - energy imported in milli-watthours in last period + * @param[in] aPeriodicEnergyExported - energy exported in milli-watthours in last period + */ +CHIP_ERROR EVSEManufacturer::SendPeriodicEnergyReading(EndpointId aEndpointId, int64_t aPeriodicEnergyImported, + int64_t aPeriodicEnergyExported) +{ + MeasurementData * data = MeasurementDataForEndpoint(aEndpointId); + VerifyOrReturnError(data != nullptr, CHIP_ERROR_UNINITIALIZED); + + EnergyMeasurementStruct::Type energyImported; + EnergyMeasurementStruct::Type energyExported; + + /** IMPORT */ + // Copy last endTimestamp into new startTimestamp if it exists + energyImported.startTimestamp.ClearValue(); + energyImported.startSystime.ClearValue(); + if (data->periodicImported.HasValue()) + { + energyImported.startTimestamp = data->periodicImported.Value().endTimestamp; + energyImported.startSystime = data->periodicImported.Value().endSystime; + } + + energyImported.energy = aPeriodicEnergyImported; + + /** EXPORT */ + // Copy last endTimestamp into new startTimestamp if it exists + energyExported.startTimestamp.ClearValue(); + energyExported.startSystime.ClearValue(); + if (data->periodicExported.HasValue()) + { + energyExported.startTimestamp = data->periodicExported.Value().endTimestamp; + energyExported.startSystime = data->periodicExported.Value().endSystime; + } + + energyExported.energy = aPeriodicEnergyExported; + + // Get current timestamp + uint32_t currentTimestamp; + CHIP_ERROR err = GetEpochTS(currentTimestamp); + if (err == CHIP_NO_ERROR) + { + // use EpochTS + energyImported.endTimestamp.SetValue(currentTimestamp); + energyExported.endTimestamp.SetValue(currentTimestamp); + } + else + { + ChipLogError(AppServer, "GetEpochTS returned error getting timestamp"); + + // use systemTime as a fallback + System::Clock::Milliseconds64 system_time_ms = + std::chrono::duration_cast(chip::Server::GetInstance().TimeSinceInit()); + uint64_t nowMS = static_cast(system_time_ms.count()); + + energyImported.endSystime.SetValue(nowMS); + energyExported.endSystime.SetValue(nowMS); + } + + // call the SDK to update attributes and generate an event + if (!NotifyPeriodicEnergyMeasured(aEndpointId, MakeOptional(energyImported), MakeOptional(energyExported))) + { + ChipLogError(AppServer, "Failed to notify Cumulative Energy reading."); + return CHIP_ERROR_INTERNAL; + } + + return CHIP_NO_ERROR; +} + struct FakeReadingsData { - bool bEnabled; /* If enabled then the timer callback will re-trigger */ - EndpointId mEndpointId; /* Which endpoint the meter is on */ - uint8_t mInterval_s; /* Interval in seconds to callback */ - int64_t mPower_mW; /* Power on the load in mW (signed value) +ve = imported */ - uint32_t mPowerRandomness_mW; /* The amount to randomize the Power on the load in mW */ + bool bEnabled; /* If enabled then the timer callback will re-trigger */ + EndpointId mEndpointId; /* Which endpoint the meter is on */ + uint8_t mInterval_s; /* Interval in seconds to callback */ + int64_t mPower_mW; /* Active Power on the load in mW (signed value) +ve = imported */ + uint32_t mPowerRandomness_mW; /* The amount to randomize the Power on the load in mW */ + int64_t mVoltage_mV; /* Voltage reading in mV (signed value) */ + uint32_t mVoltageRandomness_mV; /* The amount to randomize the Voltage in mV */ + int64_t mCurrent_mA; /* ActiveCurrent reading in mA (signed value) */ + uint32_t mCurrentRandomness_mA; /* The amount to randomize the ActiveCurrent in mA */ + /* These energy values can only be positive values. * however the underlying energy type (power_mWh) is signed, so keeping with that convention */ - int64_t mTotalEnergyImported = 0; /* Energy Imported which is updated if mPower > 0 */ - int64_t mTotalEnergyExported = 0; /* Energy Imported which is updated if mPower < 0 */ + int64_t mTotalEnergyImported = 0; /* Cumulative Energy Imported which is updated if mPower > 0 */ + int64_t mTotalEnergyExported = 0; /* Cumulative Energy Imported which is updated if mPower < 0 */ + int64_t mPeriodicEnergyImported = 0; /* Periodic Energy Imported which is updated if mPower > 0 */ + int64_t mPeriodicEnergyExported = 0; /* Periodic Energy Imported which is updated if mPower < 0 */ }; static FakeReadingsData gFakeReadingsData; @@ -188,20 +310,30 @@ static FakeReadingsData gFakeReadingsData; * @param[in] aPower_mW - the mean power of the load * Positive power indicates Imported energy (e.g. a load) * Negative power indicated Exported energy (e.g. a generator) - * @param[in] aPowerRandomness_mW This is used to define the std.dev of the + * @param[in] aPowerRandomness_mW This is used to define the max randomness of the * random power values around the mean power of the load - * + * @param[in] aVoltage_mV - the nominal voltage measurement + * @param[in] aVoltageRandomness_mV This is used to define the max randomness of the + * random voltage values + * @param[in] aCurrent_mA - the nominal current measurement + * @param[in] aCurrentRandomness_mA This is used to define the max randomness of the + * random current values * @param[in] aInterval_s - the callback interval in seconds * @param[in] bReset - boolean: true will reset the energy values to 0 */ void EVSEManufacturer::StartFakeReadings(EndpointId aEndpointId, int64_t aPower_mW, uint32_t aPowerRandomness_mW, - uint8_t aInterval_s, bool bReset) + int64_t aVoltage_mV, uint32_t aVoltageRandomness_mV, int64_t aCurrent_mA, + uint32_t aCurrentRandomness_mA, uint8_t aInterval_s, bool bReset) { - gFakeReadingsData.bEnabled = true; - gFakeReadingsData.mEndpointId = aEndpointId; - gFakeReadingsData.mPower_mW = aPower_mW; - gFakeReadingsData.mPowerRandomness_mW = aPowerRandomness_mW; - gFakeReadingsData.mInterval_s = aInterval_s; + gFakeReadingsData.bEnabled = true; + gFakeReadingsData.mEndpointId = aEndpointId; + gFakeReadingsData.mPower_mW = aPower_mW; + gFakeReadingsData.mPowerRandomness_mW = aPowerRandomness_mW; + gFakeReadingsData.mVoltage_mV = aVoltage_mV; + gFakeReadingsData.mVoltageRandomness_mV = aVoltageRandomness_mV; + gFakeReadingsData.mCurrent_mA = aCurrent_mA; + gFakeReadingsData.mCurrentRandomness_mA = aCurrentRandomness_mA; + gFakeReadingsData.mInterval_s = aInterval_s; if (bReset) { @@ -230,29 +362,51 @@ void EVSEManufacturer::FakeReadingsUpdate() return; } - // Update meter values + // Update readings // Avoid using floats - so we will do a basic rand() call which will generate a integer value between 0 and RAND_MAX // first compute power as a mean + some random value in range +/- mPowerRandomness_mW int64_t power = (static_cast(rand()) % (2 * gFakeReadingsData.mPowerRandomness_mW)) - gFakeReadingsData.mPowerRandomness_mW; power += gFakeReadingsData.mPower_mW; // add in the base power - // TODO call the EPM cluster to send a power reading + int64_t voltage = + (static_cast(rand()) % (2 * gFakeReadingsData.mVoltageRandomness_mV)) - gFakeReadingsData.mVoltageRandomness_mV; + voltage += gFakeReadingsData.mVoltage_mV; // add in the base voltage + + /* Note: whilst we could compute a current from the power and voltage, + * there will always be some random error from the sensor + * that measures it. To keep this simple and to avoid doing divides in integer + * format etc use the same approach here too. + * This is meant more as an example to show how to use the APIs, not + * to be a real representation of laws of physics. + */ + int64_t current = + (static_cast(rand()) % (2 * gFakeReadingsData.mCurrentRandomness_mA)) - gFakeReadingsData.mCurrentRandomness_mA; + current += gFakeReadingsData.mCurrent_mA; // add in the base current + + SendPowerReading(gFakeReadingsData.mEndpointId, power, voltage, current); // update the energy meter - we'll assume that the power has been constant during the previous interval if (gFakeReadingsData.mPower_mW > 0) { // Positive power - means power is imported - gFakeReadingsData.mTotalEnergyImported += ((power * gFakeReadingsData.mInterval_s) / 3600); + gFakeReadingsData.mPeriodicEnergyImported = ((power * gFakeReadingsData.mInterval_s) / 3600); + gFakeReadingsData.mPeriodicEnergyExported = 0; + gFakeReadingsData.mTotalEnergyImported += gFakeReadingsData.mPeriodicEnergyImported; } else { - // Negative power - means power is exported, but the cumulative energy is positive - gFakeReadingsData.mTotalEnergyExported += ((-power * gFakeReadingsData.mInterval_s) / 3600); + // Negative power - means power is exported, but the exported energy is reported positive + gFakeReadingsData.mPeriodicEnergyImported = 0; + gFakeReadingsData.mPeriodicEnergyExported = ((-power * gFakeReadingsData.mInterval_s) / 3600); + gFakeReadingsData.mTotalEnergyExported += gFakeReadingsData.mPeriodicEnergyExported; } - SendEnergyReading(gFakeReadingsData.mEndpointId, gFakeReadingsData.mTotalEnergyImported, - gFakeReadingsData.mTotalEnergyExported); + SendPeriodicEnergyReading(gFakeReadingsData.mEndpointId, gFakeReadingsData.mPeriodicEnergyImported, + gFakeReadingsData.mPeriodicEnergyExported); + + SendCumulativeEnergyReading(gFakeReadingsData.mEndpointId, gFakeReadingsData.mTotalEnergyImported, + gFakeReadingsData.mTotalEnergyExported); // start/restart the timer DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(gFakeReadingsData.mInterval_s), FakeReadingsTimerExpiry, this); @@ -322,7 +476,7 @@ EnergyEvseDelegate * GetEvseDelegate() { EVSEManufacturer * mn = GetEvseManufacturer(); VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); - EnergyEvseDelegate * dg = mn->GetDelegate(); + EnergyEvseDelegate * dg = mn->GetEvseDelegate(); VerifyOrDieWithMsg(dg != nullptr, AppServer, "EVSE Delegate is null"); return dg; @@ -414,11 +568,16 @@ void SetTestEventTrigger_FakeReadingsLoadStart() EVSEManufacturer * mn = GetEvseManufacturer(); VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); - int64_t aPower_mW = 1'000'000; // Fake load 1000 W - uint32_t aPowerRandomness_mW = 20'000; // randomness 20W - uint8_t aInterval_s = 2; // 2s updates - bool bReset = true; - mn->StartFakeReadings(EndpointId(1), aPower_mW, aPowerRandomness_mW, aInterval_s, bReset); + int64_t aPower_mW = 1'000'000; // Fake load 1000 W + uint32_t aPowerRandomness_mW = 20'000; // randomness 20W + int64_t aVoltage_mV = 230'000; // Fake Voltage 230V + uint32_t aVoltageRandomness_mV = 1'000; // randomness 1V + int64_t aCurrent_mA = 4'348; // Fake Current (at 1kW@230V = 4.3478 Amps) + uint32_t aCurrentRandomness_mA = 500; // randomness 500mA + uint8_t aInterval_s = 2; // 2s updates + bool bReset = true; + mn->StartFakeReadings(EndpointId(1), aPower_mW, aPowerRandomness_mW, aVoltage_mV, aVoltageRandomness_mV, aCurrent_mA, + aCurrentRandomness_mA, aInterval_s, bReset); } void SetTestEventTrigger_FakeReadingsGeneratorStart() @@ -426,11 +585,16 @@ void SetTestEventTrigger_FakeReadingsGeneratorStart() EVSEManufacturer * mn = GetEvseManufacturer(); VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); - int64_t aPower_mW = -3'000'000; // Fake Generator -3000 W - uint32_t aPowerRandomness_mW = 20'000; // randomness 20W - uint8_t aInterval_s = 5; // 5s updates - bool bReset = true; - mn->StartFakeReadings(EndpointId(1), aPower_mW, aPowerRandomness_mW, aInterval_s, bReset); + int64_t aPower_mW = -3'000'000; // Fake Generator -3000 W + uint32_t aPowerRandomness_mW = 20'000; // randomness 20W + int64_t aVoltage_mV = 230'000; // Fake Voltage 230V + uint32_t aVoltageRandomness_mV = 1'000; // randomness 1V + int64_t aCurrent_mA = -13'043; // Fake Current (at -3kW@230V = -13.0434 Amps) + uint32_t aCurrentRandomness_mA = 500; // randomness 500mA + uint8_t aInterval_s = 5; // 5s updates + bool bReset = true; + mn->StartFakeReadings(EndpointId(1), aPower_mW, aPowerRandomness_mW, aVoltage_mV, aVoltageRandomness_mV, aCurrent_mA, + aCurrentRandomness_mA, aInterval_s, bReset); } void SetTestEventTrigger_FakeReadingsStop() diff --git a/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp b/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp new file mode 100644 index 00000000000000..fe712d92ae1667 --- /dev/null +++ b/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp @@ -0,0 +1,511 @@ +/* + * + * 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 +#include + +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::DataModel; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::ElectricalPowerMeasurement; + +CHIP_ERROR ElectricalPowerMeasurementInstance::Init() +{ + return Instance::Init(); +} + +void ElectricalPowerMeasurementInstance::Shutdown() +{ + Instance::Shutdown(); +} + +// --------------- Internal Attribute Set APIs +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetPowerMode(PowerModeEnum newValue) +{ + PowerModeEnum oldValue = mPowerMode; + + if (EnsureKnownEnumValue(newValue) == PowerModeEnum::kUnknownEnumValue) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + + mPowerMode = newValue; + if (oldValue != newValue) + { + ChipLogDetail(AppServer, "mPowerMode updated to %d", static_cast(mPowerMode)); + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, PowerMode::Id); + } + + return CHIP_NO_ERROR; +} + +const MeasurementAccuracyRangeStruct::Type activePowerAccuracyRanges[] = { + // 2 - 5%, 3% Typ + { + .rangeMin = -50'000'000, // -50kW + .rangeMax = -10'000'000, // -10kW + .percentMax = MakeOptional(static_cast(5000)), + .percentMin = MakeOptional(static_cast(2000)), + .percentTypical = MakeOptional(static_cast(3000)), + }, + // 0.1 - 1%, 0.5% Typ + { + .rangeMin = -9'999'999, // -9.999kW + .rangeMax = 9'999'999, // 9.999kW + .percentMax = MakeOptional(static_cast(1000)), + .percentMin = MakeOptional(static_cast(100)), + .percentTypical = MakeOptional(static_cast(500)), + }, + // 2 - 5%, 3% Typ + { + .rangeMin = 10'000'000, // 10 kW + .rangeMax = 50'000'000, // 50 kW + .percentMax = MakeOptional(static_cast(5000)), + .percentMin = MakeOptional(static_cast(2000)), + .percentTypical = MakeOptional(static_cast(3000)), + }, +}; + +const MeasurementAccuracyRangeStruct::Type activeCurrentAccuracyRanges[] = { + // 2 - 5%, 3% Typ + { + .rangeMin = -100'000, // -100A + .rangeMax = -5'000, // -5A + .percentMax = MakeOptional(static_cast(5000)), + .percentMin = MakeOptional(static_cast(2000)), + .percentTypical = MakeOptional(static_cast(3000)), + }, + // 0.1 - 1%, 0.5% Typ + { + .rangeMin = -4'999, // -4.999A + .rangeMax = 4'999, // 4.999A + .percentMax = MakeOptional(static_cast(1000)), + .percentMin = MakeOptional(static_cast(100)), + .percentTypical = MakeOptional(static_cast(500)), + }, + // 2 - 5%, 3% Typ + { + .rangeMin = 5'000, // 5A + .rangeMax = 100'000, // 100 A + .percentMax = MakeOptional(static_cast(5000)), + .percentMin = MakeOptional(static_cast(2000)), + .percentTypical = MakeOptional(static_cast(3000)), + }, +}; + +const MeasurementAccuracyRangeStruct::Type voltageAccuracyRanges[] = { + // 2 - 5%, 3% Typ + { + .rangeMin = -500'000, // -500V + .rangeMax = -100'000, // -100V + .percentMax = MakeOptional(static_cast(5000)), + .percentMin = MakeOptional(static_cast(2000)), + .percentTypical = MakeOptional(static_cast(3000)), + }, + // 0.1 - 1%, 0.5% Typ + { + .rangeMin = -99'999, // -99.999V + .rangeMax = 99'999, // 99.999V + .percentMax = MakeOptional(static_cast(1000)), + .percentMin = MakeOptional(static_cast(100)), + .percentTypical = MakeOptional(static_cast(500)), + }, + // 2 - 5%, 3% Typ + { + .rangeMin = 100'000, // 100 V + .rangeMax = 500'000, // 500 V + .percentMax = MakeOptional(static_cast(5000)), + .percentMin = MakeOptional(static_cast(2000)), + .percentTypical = MakeOptional(static_cast(3000)), + } +}; + +static const Structs::MeasurementAccuracyStruct::Type kMeasurementAccuracies[] = { + { + .measurementType = MeasurementTypeEnum::kActivePower, + .measured = true, + .minMeasuredValue = -50'000'000, // -50 kW + .maxMeasuredValue = 50'000'000, // 50 kW + .accuracyRanges = DataModel::List(activePowerAccuracyRanges), + }, + { + .measurementType = MeasurementTypeEnum::kActiveCurrent, + .measured = true, + .minMeasuredValue = -100'000, // -100A + .maxMeasuredValue = 100'000, // 100A + .accuracyRanges = DataModel::List(activeCurrentAccuracyRanges), + }, + { + .measurementType = MeasurementTypeEnum::kVoltage, + .measured = true, + .minMeasuredValue = -500'000, // -500V + .maxMeasuredValue = 500'000, // 500V + .accuracyRanges = DataModel::List(voltageAccuracyRanges), + }, +}; + +uint8_t ElectricalPowerMeasurementDelegate::GetNumberOfMeasurementTypes() +{ + return ArraySize(kMeasurementAccuracies); +}; + +/* @brief This function is called by the cluster server at the start of read cycle + * This could take a semaphore to stop a background update of the data + */ +CHIP_ERROR ElectricalPowerMeasurementDelegate::StartAccuracyRead() +{ + /* Since we have a static array we don't need to do anything here */ + return CHIP_NO_ERROR; +} + +CHIP_ERROR ElectricalPowerMeasurementDelegate::GetAccuracyByIndex(uint8_t accuracyIndex, + Structs::MeasurementAccuracyStruct::Type & accuracy) +{ + if (accuracyIndex >= ArraySize(kMeasurementAccuracies)) + { + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + } + + accuracy = kMeasurementAccuracies[accuracyIndex]; + + return CHIP_NO_ERROR; +} + +/* @brief This function is called by the cluster server at the end of read cycle + * This could release a semaphore to allow a background update of the data + */ +CHIP_ERROR ElectricalPowerMeasurementDelegate::EndAccuracyRead() +{ + /* Since we have a static array we don't need to do anything here */ + return CHIP_NO_ERROR; +} + +/* @brief This function is called by the cluster server at the start of read cycle + * This could take a semaphore to stop a background update of the data + */ +CHIP_ERROR ElectricalPowerMeasurementDelegate::StartRangesRead() +{ + /* Since we don't an implementation here we don't need to do anything here */ + return CHIP_NO_ERROR; +} + +CHIP_ERROR ElectricalPowerMeasurementDelegate::GetRangeByIndex(uint8_t rangeIndex, Structs::MeasurementRangeStruct::Type & range) +{ + /** TODO - Manufacturers wanting to support this should + * implement an array of + * Structs::MeasurementRangeStruct::Type mMeasurementRanges[]; + * + * their application code should update the relevant measurement 'Range' information including + * - .measurementType + * - .min + * - .max + * - .startTimestamp + * - .endTimestamp + * - .minTimestamp (the time at which the minimum value was recorded) + * - .maxTimestamp (the time at which the maximum value was recorded) + * (and optionally use sys time equivalents) + * + * if (rangeIndex >= ArraySize(mMeasurementRanges)) + * { + * return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + * } + * + * range = mMeasurementRanges[rangeIndex]; + * + * return CHIP_NO_ERROR; + */ + + /* Return an empty list for now */ + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; +} + +/* @brief This function is called by the cluster server at the end of read cycle + * This could release a semaphore to allow a background update of the data + */ +CHIP_ERROR ElectricalPowerMeasurementDelegate::EndRangesRead() +{ + /* Since we don't an implementation here we don't need to do anything here */ + return CHIP_NO_ERROR; +} + +/* @brief This function is called by the cluster server at the start of read cycle + * This could take a semaphore to stop a background update of the data + */ +CHIP_ERROR ElectricalPowerMeasurementDelegate::StartHarmonicCurrentsRead() +{ + /* Since we don't an implementation here we don't need to do anything here */ + return CHIP_NO_ERROR; +} +CHIP_ERROR +ElectricalPowerMeasurementDelegate::GetHarmonicCurrentsByIndex(uint8_t harmonicCurrentsIndex, + Structs::HarmonicMeasurementStruct::Type & harmonicCurrent) +{ + /** TODO - Manufacturers wanting to support this could implement an array of + * Structs::HarmonicMeasurementStruct::Type mHarmonicCurrentMeasurements[]; + * + * The application code should update the relevant harmonic 'order' information including + * - .order + * - .measurement + * + * The application should also ensure it notifies remote clients that the value has changed + * MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, HarmonicCurrents::Id); + */ + + /* if (rangeIndex >= ArraySize(mHarmonicCurrentMeasurements)) + * { + * return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + * } + * + * range = mHarmonicCurrentMeasurements[rangeIndex]; + * + * return CHIP_NO_ERROR; + */ + + /* Return an empty list for now */ + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; +} +/* @brief This function is called by the cluster server at the end of read cycle + * This could release a semaphore to allow a background update of the data + */ +CHIP_ERROR ElectricalPowerMeasurementDelegate::EndHarmonicCurrentsRead() +{ + /* Since we don't an implementation here we don't need to do anything here */ + return CHIP_NO_ERROR; +} + +/* @brief This function is called by the cluster server at the start of read cycle + * This could take a semaphore to stop a background update of the data + */ +CHIP_ERROR ElectricalPowerMeasurementDelegate::StartHarmonicPhasesRead() +{ + /* Since we don't an implementation here we don't need to do anything here */ + return CHIP_NO_ERROR; +} + +CHIP_ERROR ElectricalPowerMeasurementDelegate::GetHarmonicPhasesByIndex(uint8_t harmonicPhaseIndex, + Structs::HarmonicMeasurementStruct::Type & harmonicPhase) +{ + /** TODO - Manufacturers wanting to support this could implement an array of + * Structs::HarmonicMeasurementStruct::Type mHarmonicPhaseMeasurements[]; + * + * The application code should update the relevant harmonic 'order' information including + * - .order + * - .measurement + * + * The application should also ensure it notifies remote clients that the value has changed + * MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, HarmonicPhases::Id); + */ + + /* if (rangeIndex >= ArraySize(mHarmonicPhaseMeasurements)) + * { + * return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + * } + * + * range = mHarmonicPhaseMeasurements[rangeIndex]; + * + * return CHIP_NO_ERROR; + */ + + /* Return an empty list for now */ + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; +} +/* @brief This function is called by the cluster server at the end of read cycle + * This could release a semaphore to allow a background update of the data + */ +CHIP_ERROR ElectricalPowerMeasurementDelegate::EndHarmonicPhasesRead() +{ + /* Since we don't an implementation here we don't need to do anything here */ + return CHIP_NO_ERROR; +} + +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetVoltage(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mVoltage; + + mVoltage = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, Voltage::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetActiveCurrent(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mActiveCurrent; + + mActiveCurrent = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, ActiveCurrent::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetReactiveCurrent(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mReactiveCurrent; + + mReactiveCurrent = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, ReactiveCurrent::Id); + } + + return CHIP_NO_ERROR; +} +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetApparentCurrent(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mApparentCurrent; + + mApparentCurrent = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, ApparentCurrent::Id); + } + + return CHIP_NO_ERROR; +} +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetActivePower(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mActivePower; + + mActivePower = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, ActivePower::Id); + } + + return CHIP_NO_ERROR; +} +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetReactivePower(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mReactivePower; + + mReactivePower = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, ReactivePower::Id); + } + + return CHIP_NO_ERROR; +} +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetApparentPower(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mApparentPower; + + mApparentPower = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, ApparentPower::Id); + } + + return CHIP_NO_ERROR; +} +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetRMSVoltage(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mRMSVoltage; + + mRMSVoltage = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, RMSVoltage::Id); + } + + return CHIP_NO_ERROR; +} +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetRMSCurrent(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mRMSCurrent; + + mRMSCurrent = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, RMSCurrent::Id); + } + + return CHIP_NO_ERROR; +} +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetRMSPower(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mRMSPower; + + mRMSPower = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, RMSPower::Id); + } + + return CHIP_NO_ERROR; +} +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetFrequency(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mFrequency; + + mFrequency = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, Frequency::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetPowerFactor(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mPowerFactor; + + mPowerFactor = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, PowerFactor::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ElectricalPowerMeasurementDelegate::SetNeutralCurrent(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mNeutralCurrent; + + mNeutralCurrent = newValue; + if (oldValue != newValue) + { + // We won't log raw values since these could change frequently + MatterReportingAttributeChangeCallback(mEndpointId, ElectricalPowerMeasurement::Id, NeutralCurrent::Id); + } + + return CHIP_NO_ERROR; +} diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseMain.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseMain.cpp new file mode 100644 index 00000000000000..92593efe6f8370 --- /dev/null +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseMain.cpp @@ -0,0 +1,411 @@ +/* + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ENERGY_EVSE_ENDPOINT 1 + +using namespace chip; +using namespace chip::app; +using namespace chip::app::DataModel; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::EnergyEvse; +using namespace chip::app::Clusters::DeviceEnergyManagement; +using namespace chip::app::Clusters::ElectricalPowerMeasurement; +using namespace chip::app::Clusters::ElectricalEnergyMeasurement; + +static std::unique_ptr gEvseDelegate; +static std::unique_ptr gEvseInstance; +static std::unique_ptr gDEMDelegate; +static std::unique_ptr gDEMInstance; +static std::unique_ptr gEvseManufacturer; +static std::unique_ptr gEPMDelegate; +static std::unique_ptr gEPMInstance; +// Electrical Energy Measurement cluster uses ember to initialise +static std::unique_ptr gEEMAttrAccess; + +EVSEManufacturer * EnergyEvse::GetEvseManufacturer() +{ + return gEvseManufacturer.get(); +} + +/* + * @brief Creates a Delegate and Instance for DEM + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR DeviceEnergyManagementInit() +{ + if (gDEMDelegate || gDEMInstance) + { + ChipLogError(AppServer, "DEM Instance or Delegate already exist."); + return CHIP_ERROR_INCORRECT_STATE; + } + + gDEMDelegate = std::make_unique(); + if (!gDEMDelegate) + { + ChipLogError(AppServer, "Failed to allocate memory for DeviceEnergyManagementDelegate"); + return CHIP_ERROR_NO_MEMORY; + } + + /* Manufacturer may optionally not support all features, commands & attributes */ + gDEMInstance = std::make_unique( + EndpointId(ENERGY_EVSE_ENDPOINT), *gDEMDelegate, + BitMask( + DeviceEnergyManagement::Feature::kPowerAdjustment, DeviceEnergyManagement::Feature::kPowerForecastReporting, + DeviceEnergyManagement::Feature::kStateForecastReporting, DeviceEnergyManagement::Feature::kStartTimeAdjustment, + DeviceEnergyManagement::Feature::kPausable)); + + if (!gDEMInstance) + { + ChipLogError(AppServer, "Failed to allocate memory for DeviceEnergyManagementManager"); + gDEMDelegate.reset(); + return CHIP_ERROR_NO_MEMORY; + } + + CHIP_ERROR err = gDEMInstance->Init(); /* Register Attribute & Command handlers */ + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Init failed on gDEMInstance"); + gDEMInstance.reset(); + gDEMDelegate.reset(); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceEnergyManagementShutdown() +{ + /* Do this in the order Instance first, then delegate + * Ensure we call the Instance->Shutdown to free attribute & command handlers first + */ + if (gDEMInstance) + { + /* deregister attribute & command handlers */ + gDEMInstance->Shutdown(); + gDEMInstance.reset(); + } + if (gDEMDelegate) + { + gDEMDelegate.reset(); + } + return CHIP_NO_ERROR; +} + +/* + * @brief Creates a Delegate and Instance for EVSE cluster + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR EnergyEvseInit() +{ + CHIP_ERROR err; + + if (gEvseDelegate || gEvseInstance) + { + ChipLogError(AppServer, "EVSE Instance or Delegate already exist."); + return CHIP_ERROR_INCORRECT_STATE; + } + + gEvseDelegate = std::make_unique(); + if (!gEvseDelegate) + { + ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseDelegate"); + return CHIP_ERROR_NO_MEMORY; + } + + /* Manufacturer may optionally not support all features, commands & attributes */ + gEvseInstance = std::make_unique( + EndpointId(ENERGY_EVSE_ENDPOINT), *gEvseDelegate, + BitMask(EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, + EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, + EnergyEvse::Feature::kV2x), + BitMask(EnergyEvse::OptionalAttributes::kSupportsUserMaximumChargingCurrent, + EnergyEvse::OptionalAttributes::kSupportsRandomizationWindow, + EnergyEvse::OptionalAttributes::kSupportsApproximateEvEfficiency), + BitMask(EnergyEvse::OptionalCommands::kSupportsStartDiagnostics)); + + if (!gEvseInstance) + { + ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseManager"); + gEvseDelegate.reset(); + return CHIP_ERROR_NO_MEMORY; + } + + err = gEvseInstance->Init(); /* Register Attribute & Command handlers */ + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Init failed on gEvseInstance"); + gEvseInstance.reset(); + gEvseDelegate.reset(); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR EnergyEvseShutdown() +{ + /* Do this in the order Instance first, then delegate + * Ensure we call the Instance->Shutdown to free attribute & command handlers first + */ + if (gEvseInstance) + { + /* deregister attribute & command handlers */ + gEvseInstance->Shutdown(); + gEvseInstance.reset(); + } + + if (gEvseDelegate) + { + gEvseDelegate.reset(); + } + + return CHIP_NO_ERROR; +} + +/* + * @brief Creates a Delegate and Instance for Electrical Power/Energy Measurement clusters + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR EnergyMeterInit() +{ + CHIP_ERROR err; + + if (gEPMDelegate || gEPMInstance) + { + ChipLogError(AppServer, "EPM Instance or Delegate already exist."); + return CHIP_ERROR_INCORRECT_STATE; + } + + gEPMDelegate = std::make_unique(); + if (!gEPMDelegate) + { + ChipLogError(AppServer, "Failed to allocate memory for EPM Delegate"); + return CHIP_ERROR_NO_MEMORY; + } + + /* Manufacturer may optionally not support all features, commands & attributes */ + gEPMInstance = std::make_unique( + EndpointId(ENERGY_EVSE_ENDPOINT), *gEPMDelegate, + BitMask(ElectricalPowerMeasurement::Feature::kAlternatingCurrent), + BitMask( + ElectricalPowerMeasurement::OptionalAttributes::kOptionalAttributeRanges, + ElectricalPowerMeasurement::OptionalAttributes::kOptionalAttributeVoltage, + ElectricalPowerMeasurement::OptionalAttributes::kOptionalAttributeActiveCurrent)); + + if (!gEPMInstance) + { + ChipLogError(AppServer, "Failed to allocate memory for EPM Instance"); + gEPMDelegate.reset(); + return CHIP_ERROR_NO_MEMORY; + } + + err = gEPMInstance->Init(); /* Register Attribute & Command handlers */ + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Init failed on gEPMInstance"); + gEPMInstance.reset(); + gEPMDelegate.reset(); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR EnergyMeterShutdown() +{ + /* Do this in the order Instance first, then delegate + * Ensure we call the Instance->Shutdown to free attribute & command handlers first + */ + if (gEPMInstance) + { + /* deregister attribute & command handlers */ + gEPMInstance->Shutdown(); + gEPMInstance.reset(); + } + + if (gEPMDelegate) + { + gEPMDelegate.reset(); + } + + return CHIP_NO_ERROR; +} + +/* + * @brief Creates a EVSEManufacturer class to hold the EVSE & DEM clusters + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR EVSEManufacturerInit() +{ + CHIP_ERROR err; + + if (gEvseManufacturer) + { + ChipLogError(AppServer, "EvseManufacturer already exist."); + return CHIP_ERROR_INCORRECT_STATE; + } + + /* Now create EVSEManufacturer */ + gEvseManufacturer = std::make_unique(gEvseInstance.get(), gEPMInstance.get()); + if (!gEvseManufacturer) + { + ChipLogError(AppServer, "Failed to allocate memory for EvseManufacturer"); + return CHIP_ERROR_NO_MEMORY; + } + + /* Call Manufacturer specific init */ + err = gEvseManufacturer->Init(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Init failed on gEvseManufacturer"); + gEvseManufacturer.reset(); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR EVSEManufacturerShutdown() +{ + if (gEvseManufacturer) + { + /* Shutdown the EVSEManufacturer */ + gEvseManufacturer->Shutdown(); + gEvseManufacturer.reset(); + } + + return CHIP_NO_ERROR; +} + +void EvseApplicationInit() +{ + if (DeviceEnergyManagementInit() != CHIP_NO_ERROR) + { + return; + } + + if (EnergyEvseInit() != CHIP_NO_ERROR) + { + DeviceEnergyManagementShutdown(); + return; + } + + if (EnergyMeterInit() != CHIP_NO_ERROR) + { + DeviceEnergyManagementShutdown(); + EnergyEvseShutdown(); + return; + } + + if (EVSEManufacturerInit() != CHIP_NO_ERROR) + { + DeviceEnergyManagementShutdown(); + EnergyEvseShutdown(); + EnergyMeterShutdown(); + return; + } +} + +void EvseApplicationShutdown() +{ + ChipLogDetail(AppServer, "Energy Management App: ApplicationShutdown()"); + + /* Shutdown in reverse order that they were created */ + EVSEManufacturerShutdown(); /* Free the EVSEManufacturer */ + EnergyMeterShutdown(); /* Free the Energy Meter */ + EnergyEvseShutdown(); /* Free the EnergyEvse */ + DeviceEnergyManagementShutdown(); /* Free the DEM */ + + Clusters::DeviceEnergyManagementMode::Shutdown(); + Clusters::EnergyEvseMode::Shutdown(); +} + +void emberAfElectricalEnergyMeasurementClusterInitCallback(chip::EndpointId endpointId) +{ + VerifyOrDie(endpointId == 1); // this cluster is only enabled for endpoint 1. + VerifyOrDie(!gEEMAttrAccess); + + gEEMAttrAccess = std::make_unique( + BitMask( + ElectricalEnergyMeasurement::Feature::kImportedEnergy, ElectricalEnergyMeasurement::Feature::kExportedEnergy, + ElectricalEnergyMeasurement::Feature::kCumulativeEnergy, ElectricalEnergyMeasurement::Feature::kPeriodicEnergy), + BitMask( + ElectricalEnergyMeasurement::OptionalAttributes::kOptionalAttributeCumulativeEnergyReset)); + + // Create an accuracy entry which is between +/-0.5 and +/- 5% across the range of all possible energy readings + ElectricalEnergyMeasurement::Structs::MeasurementAccuracyRangeStruct::Type energyAccuracyRanges[] = { + { .rangeMin = 0, + .rangeMax = 1'000'000'000'000'000, // 1 million Mwh + .percentMax = MakeOptional(static_cast(500)), + .percentMin = MakeOptional(static_cast(50)) } + }; + + ElectricalEnergyMeasurement::Structs::MeasurementAccuracyStruct::Type accuracy = { + .measurementType = MeasurementTypeEnum::kElectricalEnergy, + .measured = true, + .minMeasuredValue = 0, + .maxMeasuredValue = 1'000'000'000'000'000, // 1 million Mwh + .accuracyRanges = + DataModel::List(energyAccuracyRanges) + }; + + // Example of setting CumulativeEnergyReset structure - for now set these to 0 + // but the manufacturer may want to store these in non volatile storage for timestamp (based on epoch_s) + ElectricalEnergyMeasurement::Structs::CumulativeEnergyResetStruct::Type resetStruct = { + .importedResetTimestamp = MakeOptional(MakeNullable(static_cast(0))), + .exportedResetTimestamp = MakeOptional(MakeNullable(static_cast(0))), + .importedResetSystime = MakeOptional(MakeNullable(static_cast(0))), + .exportedResetSystime = MakeOptional(MakeNullable(static_cast(0))), + }; + + if (gEEMAttrAccess) + { + gEEMAttrAccess->Init(); + + SetMeasurementAccuracy(endpointId, accuracy); + SetCumulativeReset(endpointId, MakeOptional(resetStruct)); + } +} diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp index 24643885d54368..f4b3941e8c1ca7 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp @@ -35,6 +35,12 @@ CHIP_ERROR EnergyEvseManager::LoadPersistentAttributes() EndpointId aEndpointId = mDelegate->GetEndpointId(); CHIP_ERROR err; + if (aProvider == nullptr) + { + ChipLogError(AppServer, "GetSafeAttributePersistenceProvider returned NULL"); + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; + } + // Restore ChargingEnabledUntil value DataModel::Nullable tempChargingEnabledUntil; err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::ChargingEnabledUntil::Id), diff --git a/examples/energy-management-app/esp32/main/CMakeLists.txt b/examples/energy-management-app/esp32/main/CMakeLists.txt index edba8f9671d1bc..ddaff6b83e02f0 100644 --- a/examples/energy-management-app/esp32/main/CMakeLists.txt +++ b/examples/energy-management-app/esp32/main/CMakeLists.txt @@ -66,6 +66,7 @@ set(SRC_DIRS_LIST "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/group-key-mgmt-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/mode-base-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/electrical-energy-measurement-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/electrical-power-measurement-server" ) set(PRIV_REQUIRES_LIST chip QRCode bt led_strip app_update openthread driver nvs_flash spi_flash) diff --git a/examples/energy-management-app/esp32/main/main.cpp b/examples/energy-management-app/esp32/main/main.cpp index 2e7e43f730a025..8132b1ba2f3aff 100644 --- a/examples/energy-management-app/esp32/main/main.cpp +++ b/examples/energy-management-app/esp32/main/main.cpp @@ -16,12 +16,7 @@ */ #include "DeviceCallbacks.h" -#include -#include -#include -#include -#include -#include +#include #include "esp_log.h" #include @@ -68,8 +63,6 @@ #include #endif -#define ENERGY_EVSE_ENDPOINT 1 - using namespace ::chip; using namespace chip::app; using namespace chip::app::Clusters; @@ -77,12 +70,6 @@ using namespace ::chip::Credentials; using namespace ::chip::DeviceManager; using namespace ::chip::DeviceLayer; -static std::unique_ptr gEvseDelegate; -static std::unique_ptr gEvseInstance; -static std::unique_ptr gDEMDelegate; -static std::unique_ptr gDEMInstance; -static std::unique_ptr gEvseManufacturer; - #if CONFIG_ENABLE_ESP_INSIGHTS_TRACE extern const char insights_auth_key_start[] asm("_binary_insights_auth_key_txt_start"); extern const char insights_auth_key_end[] asm("_binary_insights_auth_key_txt_end"); @@ -121,233 +108,16 @@ chip::Credentials::DeviceAttestationCredentialsProvider * get_dac_provider(void) } // namespace -EVSEManufacturer * EnergyEvse::GetEvseManufacturer() -{ - return gEvseManufacturer.get(); -} - -/* - * @brief Creates a Delegate and Instance for DEM - * - * The Instance is a container around the Delegate, so - * create the Delegate first, then wrap it in the Instance - * Then call the Instance->Init() to register the attribute and command handlers - */ -CHIP_ERROR DeviceEnergyManagementInit() -{ - if (gDEMDelegate || gDEMInstance) - { - ESP_LOGE(TAG, "DEM Instance or Delegate already exist."); - return CHIP_ERROR_INCORRECT_STATE; - } - - gDEMDelegate = std::make_unique(); - if (!gDEMDelegate) - { - ESP_LOGE(TAG, "Failed to allocate memory for DeviceEnergyManagementDelegate"); - return CHIP_ERROR_NO_MEMORY; - } - - /* Manufacturer may optionally not support all features, commands & attributes */ - gDEMInstance = std::make_unique( - EndpointId(ENERGY_EVSE_ENDPOINT), *gDEMDelegate, - BitMask( - DeviceEnergyManagement::Feature::kPowerAdjustment, DeviceEnergyManagement::Feature::kPowerForecastReporting, - DeviceEnergyManagement::Feature::kStateForecastReporting, DeviceEnergyManagement::Feature::kStartTimeAdjustment, - DeviceEnergyManagement::Feature::kPausable)); - - if (!gDEMInstance) - { - ESP_LOGE(TAG, "Failed to allocate memory for DeviceEnergyManagementManager"); - gDEMDelegate.reset(); - return CHIP_ERROR_NO_MEMORY; - } - - CHIP_ERROR err = gDEMInstance->Init(); /* Register Attribute & Command handlers */ - if (err != CHIP_NO_ERROR) - { - ESP_LOGE(TAG, "Init failed on gDEMInstance, err:%" CHIP_ERROR_FORMAT, err.Format()); - gDEMInstance.reset(); - gDEMDelegate.reset(); - return err; - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR DeviceEnergyManagementShutdown() -{ - /* Do this in the order Instance first, then delegate - * Ensure we call the Instance->Shutdown to free attribute & command handlers first - */ - if (gDEMInstance) - { - /* deregister attribute & command handlers */ - gDEMInstance->Shutdown(); - gDEMInstance.reset(); - } - if (gDEMDelegate) - { - gDEMDelegate.reset(); - } - return CHIP_NO_ERROR; -} - -/* - * @brief Creates a Delegate and Instance for EVSE cluster - * - * The Instance is a container around the Delegate, so - * create the Delegate first, then wrap it in the Instance - * Then call the Instance->Init() to register the attribute and command handlers - */ -CHIP_ERROR EnergyEvseInit() -{ - CHIP_ERROR err; - - if (gEvseDelegate || gEvseInstance) - { - ESP_LOGE(TAG, "EVSE Instance or Delegate already exist."); - return CHIP_ERROR_INCORRECT_STATE; - } - - gEvseDelegate = std::make_unique(); - if (!gEvseDelegate) - { - ESP_LOGE(TAG, "Failed to allocate memory for EnergyEvseDelegate"); - return CHIP_ERROR_NO_MEMORY; - } - - /* Manufacturer may optionally not support all features, commands & attributes */ - gEvseInstance = std::make_unique( - EndpointId(ENERGY_EVSE_ENDPOINT), *gEvseDelegate, - BitMask(EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, - EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, - EnergyEvse::Feature::kV2x), - BitMask(EnergyEvse::OptionalAttributes::kSupportsUserMaximumChargingCurrent, - EnergyEvse::OptionalAttributes::kSupportsRandomizationWindow, - EnergyEvse::OptionalAttributes::kSupportsApproximateEvEfficiency), - BitMask(EnergyEvse::OptionalCommands::kSupportsStartDiagnostics)); - - if (!gEvseInstance) - { - ESP_LOGE(TAG, "Failed to allocate memory for EnergyEvseManager"); - gEvseDelegate.reset(); - return CHIP_ERROR_NO_MEMORY; - } - - err = gEvseInstance->Init(); /* Register Attribute & Command handlers */ - if (err != CHIP_NO_ERROR) - { - ESP_LOGE(TAG, "Init failed on gEvseInstance, err:%" CHIP_ERROR_FORMAT, err.Format()); - gEvseInstance.reset(); - gEvseDelegate.reset(); - return err; - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR EnergyEvseShutdown() -{ - /* Do this in the order Instance first, then delegate - * Ensure we call the Instance->Shutdown to free attribute & command handlers first - */ - if (gEvseInstance) - { - /* deregister attribute & command handlers */ - gEvseInstance->Shutdown(); - gEvseInstance.reset(); - } - - if (gEvseDelegate) - { - gEvseDelegate.reset(); - } - - return CHIP_NO_ERROR; -} - -/* - * @brief Creates a EVSEManufacturer class to hold the EVSE & DEM clusters - * - * The Instance is a container around the Delegate, so - * create the Delegate first, then wrap it in the Instance - * Then call the Instance->Init() to register the attribute and command handlers - */ -CHIP_ERROR EVSEManufacturerInit() -{ - CHIP_ERROR err; - - if (gEvseManufacturer) - { - ESP_LOGE(TAG, "EvseManufacturer already exist."); - return CHIP_ERROR_INCORRECT_STATE; - } - - /* Now create EVSEManufacturer */ - gEvseManufacturer = std::make_unique(gEvseInstance.get()); - if (!gEvseManufacturer) - { - ESP_LOGE(TAG, "Failed to allocate memory for EvseManufacturer"); - return CHIP_ERROR_NO_MEMORY; - } - - /* Call Manufacturer specific init */ - err = gEvseManufacturer->Init(); - if (err != CHIP_NO_ERROR) - { - ESP_LOGE(TAG, "Init failed on gEvseManufacturer, err:%" CHIP_ERROR_FORMAT, err.Format()); - gEvseManufacturer.reset(); - return err; - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR EVSEManufacturerShutdown() -{ - if (gEvseManufacturer) - { - /* Shutdown the EVSEManufacturer */ - gEvseManufacturer->Shutdown(); - gEvseManufacturer.reset(); - } - - return CHIP_NO_ERROR; -} - void ApplicationInit() { - if (DeviceEnergyManagementInit() != CHIP_NO_ERROR) - { - return; - } - - if (EnergyEvseInit() != CHIP_NO_ERROR) - { - DeviceEnergyManagementShutdown(); - return; - } - - if (EVSEManufacturerInit() != CHIP_NO_ERROR) - { - DeviceEnergyManagementShutdown(); - EnergyEvseShutdown(); - return; - } + ESP_LOGD(TAG, "Energy Management App: ApplicationInit()"); + EvseApplicationInit(); } void ApplicationShutdown() { ESP_LOGD(TAG, "Energy Management App: ApplicationShutdown()"); - - /* Shutdown in reverse order that they were created */ - EVSEManufacturerShutdown(); /* Free the EVSEManufacturer */ - EnergyEvseShutdown(); /* Free the EnergyEvse */ - DeviceEnergyManagementShutdown(); /* Free the DEM */ - - Clusters::DeviceEnergyManagementMode::Shutdown(); - Clusters::EnergyEvseMode::Shutdown(); + EvseApplicationShutdown(); } static void InitServer(intptr_t context) diff --git a/examples/energy-management-app/linux/BUILD.gn b/examples/energy-management-app/linux/BUILD.gn index 11919a4e8c12a3..99acf81baad1c9 100644 --- a/examples/energy-management-app/linux/BUILD.gn +++ b/examples/energy-management-app/linux/BUILD.gn @@ -39,9 +39,10 @@ executable("chip-energy-management-app") { "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseMain.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", - "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/device-energy-management-mode.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/energy-evse-mode.cpp", "include/CHIPProjectAppConfig.h", diff --git a/examples/energy-management-app/linux/main.cpp b/examples/energy-management-app/linux/main.cpp index a150b82c4bb171..7973c16025d640 100644 --- a/examples/energy-management-app/linux/main.cpp +++ b/examples/energy-management-app/linux/main.cpp @@ -17,260 +17,18 @@ */ #include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#define ENERGY_EVSE_ENDPOINT 1 - -using namespace chip; -using namespace chip::app; -using namespace chip::app::Clusters; - -static std::unique_ptr gEvseDelegate; -static std::unique_ptr gEvseInstance; -static std::unique_ptr gDEMDelegate; -static std::unique_ptr gDEMInstance; -static std::unique_ptr gEvseManufacturer; - -EVSEManufacturer * EnergyEvse::GetEvseManufacturer() -{ - return gEvseManufacturer.get(); -} - -/* - * @brief Creates a Delegate and Instance for DEM - * - * The Instance is a container around the Delegate, so - * create the Delegate first, then wrap it in the Instance - * Then call the Instance->Init() to register the attribute and command handlers - */ -CHIP_ERROR DeviceEnergyManagementInit() -{ - if (gDEMDelegate || gDEMInstance) - { - ChipLogError(AppServer, "DEM Instance or Delegate already exist."); - return CHIP_ERROR_INCORRECT_STATE; - } - - gDEMDelegate = std::make_unique(); - if (!gDEMDelegate) - { - ChipLogError(AppServer, "Failed to allocate memory for DeviceEnergyManagementDelegate"); - return CHIP_ERROR_NO_MEMORY; - } - - /* Manufacturer may optionally not support all features, commands & attributes */ - gDEMInstance = std::make_unique( - EndpointId(ENERGY_EVSE_ENDPOINT), *gDEMDelegate, - BitMask( - DeviceEnergyManagement::Feature::kPowerAdjustment, DeviceEnergyManagement::Feature::kPowerForecastReporting, - DeviceEnergyManagement::Feature::kStateForecastReporting, DeviceEnergyManagement::Feature::kStartTimeAdjustment, - DeviceEnergyManagement::Feature::kPausable)); - - if (!gDEMInstance) - { - ChipLogError(AppServer, "Failed to allocate memory for DeviceEnergyManagementManager"); - gDEMDelegate.reset(); - return CHIP_ERROR_NO_MEMORY; - } - - CHIP_ERROR err = gDEMInstance->Init(); /* Register Attribute & Command handlers */ - if (err != CHIP_NO_ERROR) - { - ChipLogError(AppServer, "Init failed on gDEMInstance"); - gDEMInstance.reset(); - gDEMDelegate.reset(); - return err; - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR DeviceEnergyManagementShutdown() -{ - /* Do this in the order Instance first, then delegate - * Ensure we call the Instance->Shutdown to free attribute & command handlers first - */ - if (gDEMInstance) - { - /* deregister attribute & command handlers */ - gDEMInstance->Shutdown(); - gDEMInstance.reset(); - } - if (gDEMDelegate) - { - gDEMDelegate.reset(); - } - return CHIP_NO_ERROR; -} - -/* - * @brief Creates a Delegate and Instance for EVSE cluster - * - * The Instance is a container around the Delegate, so - * create the Delegate first, then wrap it in the Instance - * Then call the Instance->Init() to register the attribute and command handlers - */ -CHIP_ERROR EnergyEvseInit() -{ - CHIP_ERROR err; - - if (gEvseDelegate || gEvseInstance) - { - ChipLogError(AppServer, "EVSE Instance or Delegate already exist."); - return CHIP_ERROR_INCORRECT_STATE; - } - - gEvseDelegate = std::make_unique(); - if (!gEvseDelegate) - { - ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseDelegate"); - return CHIP_ERROR_NO_MEMORY; - } - - /* Manufacturer may optionally not support all features, commands & attributes */ - gEvseInstance = std::make_unique( - EndpointId(ENERGY_EVSE_ENDPOINT), *gEvseDelegate, - BitMask(EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, - EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, - EnergyEvse::Feature::kV2x), - BitMask(EnergyEvse::OptionalAttributes::kSupportsUserMaximumChargingCurrent, - EnergyEvse::OptionalAttributes::kSupportsRandomizationWindow, - EnergyEvse::OptionalAttributes::kSupportsApproximateEvEfficiency), - BitMask(EnergyEvse::OptionalCommands::kSupportsStartDiagnostics)); - - if (!gEvseInstance) - { - ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseManager"); - gEvseDelegate.reset(); - return CHIP_ERROR_NO_MEMORY; - } - - err = gEvseInstance->Init(); /* Register Attribute & Command handlers */ - if (err != CHIP_NO_ERROR) - { - ChipLogError(AppServer, "Init failed on gEvseInstance"); - gEvseInstance.reset(); - gEvseDelegate.reset(); - return err; - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR EnergyEvseShutdown() -{ - /* Do this in the order Instance first, then delegate - * Ensure we call the Instance->Shutdown to free attribute & command handlers first - */ - if (gEvseInstance) - { - /* deregister attribute & command handlers */ - gEvseInstance->Shutdown(); - gEvseInstance.reset(); - } - - if (gEvseDelegate) - { - gEvseDelegate.reset(); - } - - return CHIP_NO_ERROR; -} - -/* - * @brief Creates a EVSEManufacturer class to hold the EVSE & DEM clusters - * - * The Instance is a container around the Delegate, so - * create the Delegate first, then wrap it in the Instance - * Then call the Instance->Init() to register the attribute and command handlers - */ -CHIP_ERROR EVSEManufacturerInit() -{ - CHIP_ERROR err; - - if (gEvseManufacturer) - { - ChipLogError(AppServer, "EvseManufacturer already exist."); - return CHIP_ERROR_INCORRECT_STATE; - } - - /* Now create EVSEManufacturer */ - gEvseManufacturer = std::make_unique(gEvseInstance.get()); - if (!gEvseManufacturer) - { - ChipLogError(AppServer, "Failed to allocate memory for EvseManufacturer"); - return CHIP_ERROR_NO_MEMORY; - } - - /* Call Manufacturer specific init */ - err = gEvseManufacturer->Init(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(AppServer, "Init failed on gEvseManufacturer"); - gEvseManufacturer.reset(); - return err; - } - - return CHIP_NO_ERROR; -} - -CHIP_ERROR EVSEManufacturerShutdown() -{ - if (gEvseManufacturer) - { - /* Shutdown the EVSEManufacturer */ - gEvseManufacturer->Shutdown(); - gEvseManufacturer.reset(); - } - - return CHIP_NO_ERROR; -} +#include void ApplicationInit() { - if (DeviceEnergyManagementInit() != CHIP_NO_ERROR) - { - return; - } - - if (EnergyEvseInit() != CHIP_NO_ERROR) - { - DeviceEnergyManagementShutdown(); - return; - } - - if (EVSEManufacturerInit() != CHIP_NO_ERROR) - { - DeviceEnergyManagementShutdown(); - EnergyEvseShutdown(); - return; - } + ChipLogDetail(AppServer, "Energy Management App: ApplicationInit()"); + EvseApplicationInit(); } void ApplicationShutdown() { ChipLogDetail(AppServer, "Energy Management App: ApplicationShutdown()"); - - /* Shutdown in reverse order that they were created */ - EVSEManufacturerShutdown(); /* Free the EVSEManufacturer */ - EnergyEvseShutdown(); /* Free the EnergyEvse */ - DeviceEnergyManagementShutdown(); /* Free the DEM */ - - Clusters::DeviceEnergyManagementMode::Shutdown(); - Clusters::EnergyEvseMode::Shutdown(); + EvseApplicationShutdown(); } int main(int argc, char * argv[]) diff --git a/examples/shell/shell_common/BUILD.gn b/examples/shell/shell_common/BUILD.gn index 28b26af3ae211f..cb098a64598a1b 100644 --- a/examples/shell/shell_common/BUILD.gn +++ b/examples/shell/shell_common/BUILD.gn @@ -72,6 +72,7 @@ static_library("shell_common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ElectricalPowerMeasurementDelegate.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", ] diff --git a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp index a26e1a33844bef..7e9bc3d72c7d27 100644 --- a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp +++ b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp @@ -150,7 +150,7 @@ MeasurementData * MeasurementDataForEndpoint(EndpointId endpointId) return nullptr; } - if (index >= MATTER_DM_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT) + if (index >= ArraySize(gMeasurements)) { ChipLogError(NotSpecified, "Internal error: invalid/unexpected energy measurement index."); return nullptr; diff --git a/src/app/clusters/electrical-power-measurement-server/electrical-power-measurement-server.cpp b/src/app/clusters/electrical-power-measurement-server/electrical-power-measurement-server.cpp index 1b248310a686ef..5f124a3128df05 100644 --- a/src/app/clusters/electrical-power-measurement-server/electrical-power-measurement-server.cpp +++ b/src/app/clusters/electrical-power-measurement-server/electrical-power-measurement-server.cpp @@ -75,9 +75,17 @@ CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValu ReturnErrorOnFailure(aEncoder.Encode(mDelegate.GetNumberOfMeasurementTypes())); break; case Accuracy::Id: - return ReadAccuracy(aEncoder); + ReturnErrorOnFailure( + aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { return this->EncodeAccuracy(encoder); })); + break; case Ranges::Id: - return ReadRanges(aEncoder); + if (!SupportsOptAttr(OptionalAttributes::kOptionalAttributeRanges)) + { + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } + ReturnErrorOnFailure( + aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { return this->EncodeRanges(encoder); })); + break; case Voltage::Id: if (!SupportsOptAttr(OptionalAttributes::kOptionalAttributeVoltage)) { @@ -176,9 +184,19 @@ CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValu ReturnErrorOnFailure(aEncoder.Encode(mDelegate.GetFrequency())); break; case HarmonicCurrents::Id: - return ReadHarmonicCurrents(aEncoder); + VerifyOrReturnError( + HasFeature(ElectricalPowerMeasurement::Feature::kHarmonics), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE, + ChipLogError(Zcl, "Electrical Power Measurement: can not get HarmonicCurrents, feature is not supported")); + ReturnErrorOnFailure( + aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { return this->EncodeHarmonicCurrents(encoder); })); + break; case HarmonicPhases::Id: - return ReadHarmonicPhases(aEncoder); + VerifyOrReturnError( + HasFeature(ElectricalPowerMeasurement::Feature::kPowerQuality), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE, + ChipLogError(Zcl, "Electrical Power Measurement: can not get HarmonicPhases, feature is not supported")); + ReturnErrorOnFailure( + aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { return this->EncodeHarmonicPhases(encoder); })); + break; case PowerFactor::Id: if (!SupportsOptAttr(OptionalAttributes::kOptionalAttributePowerFactor)) { @@ -203,112 +221,121 @@ CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValu return CHIP_NO_ERROR; } -CHIP_ERROR Instance::ReadAccuracy(AttributeValueEncoder & aEncoder) +CHIP_ERROR Instance::EncodeAccuracy(const AttributeValueEncoder::ListEncodeHelper & encoder) { - CHIP_ERROR err = CHIP_NO_ERROR; - auto accuracies = mDelegate.IterateAccuracy(); - VerifyOrReturnError(accuracies != nullptr, CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); - if (accuracies->Count() == 0) - { - err = aEncoder.EncodeEmptyList(); - } - else + CHIP_ERROR err = CHIP_NO_ERROR; + + ReturnErrorOnFailure(mDelegate.StartAccuracyRead()); + for (uint8_t i = 0; true; i++) { - err = aEncoder.EncodeList([&accuracies](const auto & encoder) -> CHIP_ERROR { - Structs::MeasurementAccuracyStruct::Type accuracy; - while (accuracies->Next(accuracy)) - { - encoder.Encode(accuracy); - } - - return CHIP_NO_ERROR; - }); + Structs::MeasurementAccuracyStruct::Type accuracy; + + err = mDelegate.GetAccuracyByIndex(i, accuracy); + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + // Convert end of list to CHIP_NO_ERROR + err = CHIP_NO_ERROR; + goto exit; + } + + // Check if another error occurred before trying to encode + SuccessOrExit(err); + + err = encoder.Encode(accuracy); + SuccessOrExit(err); } - accuracies->Release(); + +exit: + // Tell the delegate the read is complete + mDelegate.EndAccuracyRead(); return err; } -CHIP_ERROR Instance::ReadRanges(AttributeValueEncoder & aEncoder) +CHIP_ERROR Instance::EncodeRanges(const AttributeValueEncoder::ListEncodeHelper & encoder) { - if (!SupportsOptAttr(OptionalAttributes::kOptionalAttributeRanges)) - { - return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); - } CHIP_ERROR err = CHIP_NO_ERROR; - auto ranges = mDelegate.IterateRanges(); - VerifyOrReturnError(ranges != nullptr, CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); - if (ranges->Count() == 0) - { - err = aEncoder.EncodeEmptyList(); - } - else + + ReturnErrorOnFailure(mDelegate.StartRangesRead()); + for (uint8_t i = 0; true; i++) { - err = aEncoder.EncodeList([&ranges](const auto & encoder) -> CHIP_ERROR { - Structs::MeasurementRangeStruct::Type range; - while (ranges->Next(range)) - { - encoder.Encode(range); - } - - return CHIP_NO_ERROR; - }); + Structs::MeasurementRangeStruct::Type range; + + err = mDelegate.GetRangeByIndex(i, range); + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + // Convert end of list to CHIP_NO_ERROR + err = CHIP_NO_ERROR; + goto exit; + } + + // Check if another error occurred before trying to encode + SuccessOrExit(err); + + err = encoder.Encode(range); + SuccessOrExit(err); } - ranges->Release(); + +exit: + // Tell the delegate the read is complete + err = mDelegate.EndRangesRead(); return err; } -CHIP_ERROR Instance::ReadHarmonicCurrents(AttributeValueEncoder & aEncoder) +CHIP_ERROR Instance::EncodeHarmonicCurrents(const AttributeValueEncoder::ListEncodeHelper & encoder) { - VerifyOrReturnError(HasFeature(ElectricalPowerMeasurement::Feature::kHarmonics), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE, - ChipLogError(Zcl, "Electrical Power Measurement: can not get HarmonicCurrents, feature is not supported")); - CHIP_ERROR err = CHIP_NO_ERROR; - auto currents = mDelegate.IterateHarmonicCurrents(); - VerifyOrReturnError(currents != nullptr, CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); - if (currents->Count() == 0) - { - err = aEncoder.EncodeEmptyList(); - } - else + + ReturnErrorOnFailure(mDelegate.StartHarmonicCurrentsRead()); + for (uint8_t i = 0; true; i++) { - err = aEncoder.EncodeList([¤ts](const auto & encoder) -> CHIP_ERROR { - Structs::HarmonicMeasurementStruct::Type current; - while (currents->Next(current)) - { - encoder.Encode(current); - } - - return CHIP_NO_ERROR; - }); + Structs::HarmonicMeasurementStruct::Type current; + + err = mDelegate.GetHarmonicCurrentsByIndex(i, current); + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + // Convert end of list to CHIP_NO_ERROR + err = CHIP_NO_ERROR; + goto exit; + } + // Check if another error occurred before trying to encode + SuccessOrExit(err); + + err = encoder.Encode(current); + SuccessOrExit(err); } - currents->Release(); + +exit: + // Tell the delegate the read is complete + err = mDelegate.EndHarmonicCurrentsRead(); return err; } -CHIP_ERROR Instance::ReadHarmonicPhases(AttributeValueEncoder & aEncoder) +CHIP_ERROR Instance::EncodeHarmonicPhases(const AttributeValueEncoder::ListEncodeHelper & encoder) { - VerifyOrReturnError(HasFeature(ElectricalPowerMeasurement::Feature::kPowerQuality), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE, - ChipLogError(Zcl, "Electrical Power Measurement: can not get HarmonicPhases, feature is not supported")); CHIP_ERROR err = CHIP_NO_ERROR; - auto phases = mDelegate.IterateHarmonicPhases(); - VerifyOrReturnError(phases != nullptr, CHIP_IM_GLOBAL_STATUS(ResourceExhausted)); - if (phases->Count() == 0) - { - err = aEncoder.EncodeEmptyList(); - } - else + + ReturnErrorOnFailure(mDelegate.StartHarmonicPhasesRead()); + for (uint8_t i = 0; true; i++) { - err = aEncoder.EncodeList([&phases](const auto & encoder) -> CHIP_ERROR { - Structs::HarmonicMeasurementStruct::Type phase; - while (phases->Next(phase)) - { - encoder.Encode(phase); - } - - return CHIP_NO_ERROR; - }); + Structs::HarmonicMeasurementStruct::Type phase; + + err = mDelegate.GetHarmonicPhasesByIndex(i, phase); + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + // Convert end of list to CHIP_NO_ERROR + err = CHIP_NO_ERROR; + goto exit; + } + // Check if another error occurred before trying to encode + SuccessOrExit(err); + + err = encoder.Encode(phase); + SuccessOrExit(err); } - phases->Release(); + +exit: + // Tell the delegate the read is complete + err = mDelegate.EndHarmonicPhasesRead(); return err; } diff --git a/src/app/clusters/electrical-power-measurement-server/electrical-power-measurement-server.h b/src/app/clusters/electrical-power-measurement-server/electrical-power-measurement-server.h index dff7b44b1d13a4..0b5d45f1dc447b 100644 --- a/src/app/clusters/electrical-power-measurement-server/electrical-power-measurement-server.h +++ b/src/app/clusters/electrical-power-measurement-server/electrical-power-measurement-server.h @@ -40,29 +40,46 @@ class Delegate void SetEndpointId(EndpointId aEndpoint) { mEndpointId = aEndpoint; } - using AccuracyIterator = CommonIterator; - using RangeIterator = CommonIterator; using HarmonicMeasurementIterator = CommonIterator; - virtual PowerModeEnum GetPowerMode() = 0; - virtual uint8_t GetNumberOfMeasurementTypes() = 0; - virtual AccuracyIterator * IterateAccuracy() = 0; - virtual RangeIterator * IterateRanges() = 0; - virtual DataModel::Nullable GetVoltage() = 0; - virtual DataModel::Nullable GetActiveCurrent() = 0; - virtual DataModel::Nullable GetReactiveCurrent() = 0; - virtual DataModel::Nullable GetApparentCurrent() = 0; - virtual DataModel::Nullable GetActivePower() = 0; - virtual DataModel::Nullable GetReactivePower() = 0; - virtual DataModel::Nullable GetApparentPower() = 0; - virtual DataModel::Nullable GetRMSVoltage() = 0; - virtual DataModel::Nullable GetRMSCurrent() = 0; - virtual DataModel::Nullable GetRMSPower() = 0; - virtual DataModel::Nullable GetFrequency() = 0; - virtual HarmonicMeasurementIterator * IterateHarmonicCurrents() = 0; - virtual HarmonicMeasurementIterator * IterateHarmonicPhases() = 0; - virtual DataModel::Nullable GetPowerFactor() = 0; - virtual DataModel::Nullable GetNeutralCurrent() = 0; + virtual PowerModeEnum GetPowerMode() = 0; + virtual uint8_t GetNumberOfMeasurementTypes() = 0; + + /* These functions are called by the ReadAttribute handler to iterate through lists + * The cluster server will call StartRead to allow the delegate to create a temporary + * lock on the data. + * The delegate is expected to not change these values once StartRead has been called + * until the EndRead() has been called (e.g. releasing a lock on the data) + */ + virtual CHIP_ERROR StartAccuracyRead() = 0; + virtual CHIP_ERROR GetAccuracyByIndex(uint8_t, Structs::MeasurementAccuracyStruct::Type &) = 0; + virtual CHIP_ERROR EndAccuracyRead() = 0; + + virtual CHIP_ERROR StartRangesRead() = 0; + virtual CHIP_ERROR GetRangeByIndex(uint8_t, Structs::MeasurementRangeStruct::Type &) = 0; + virtual CHIP_ERROR EndRangesRead() = 0; + + virtual CHIP_ERROR StartHarmonicCurrentsRead() = 0; + virtual CHIP_ERROR GetHarmonicCurrentsByIndex(uint8_t, Structs::HarmonicMeasurementStruct::Type &) = 0; + virtual CHIP_ERROR EndHarmonicCurrentsRead() = 0; + + virtual CHIP_ERROR StartHarmonicPhasesRead() = 0; + virtual CHIP_ERROR GetHarmonicPhasesByIndex(uint8_t, Structs::HarmonicMeasurementStruct::Type &) = 0; + virtual CHIP_ERROR EndHarmonicPhasesRead() = 0; + + virtual DataModel::Nullable GetVoltage() = 0; + virtual DataModel::Nullable GetActiveCurrent() = 0; + virtual DataModel::Nullable GetReactiveCurrent() = 0; + virtual DataModel::Nullable GetApparentCurrent() = 0; + virtual DataModel::Nullable GetActivePower() = 0; + virtual DataModel::Nullable GetReactivePower() = 0; + virtual DataModel::Nullable GetApparentPower() = 0; + virtual DataModel::Nullable GetRMSVoltage() = 0; + virtual DataModel::Nullable GetRMSCurrent() = 0; + virtual DataModel::Nullable GetRMSPower() = 0; + virtual DataModel::Nullable GetFrequency() = 0; + virtual DataModel::Nullable GetPowerFactor() = 0; + virtual DataModel::Nullable GetNeutralCurrent() = 0; protected: EndpointId mEndpointId = 0; @@ -112,10 +129,10 @@ class Instance : public AttributeAccessInterface // AttributeAccessInterface CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; - CHIP_ERROR ReadAccuracy(AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadRanges(AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadHarmonicCurrents(AttributeValueEncoder & aEncoder); - CHIP_ERROR ReadHarmonicPhases(AttributeValueEncoder & aEncoder); + CHIP_ERROR EncodeAccuracy(const AttributeValueEncoder::ListEncodeHelper & aEncoder); + CHIP_ERROR EncodeRanges(const AttributeValueEncoder::ListEncodeHelper & aEncoder); + CHIP_ERROR EncodeHarmonicCurrents(const AttributeValueEncoder::ListEncodeHelper & aEncoder); + CHIP_ERROR EncodeHarmonicPhases(const AttributeValueEncoder::ListEncodeHelper & aEncoder); }; } // namespace ElectricalPowerMeasurement diff --git a/src/python_testing/TC_EEM_2_1.py b/src/python_testing/TC_EEM_2_1.py new file mode 100644 index 00000000000000..909380401e49d6 --- /dev/null +++ b/src/python_testing/TC_EEM_2_1.py @@ -0,0 +1,86 @@ +# +# 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. + +import logging + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EnergyReporting_Utils import EnergyReportingBaseTestHelper + +logger = logging.getLogger(__name__) + + +class TC_EEM_2_1(MatterBaseTest, EnergyReportingBaseTestHelper): + + def desc_TC_EEM_2_1(self) -> str: + """Returns a description of this test""" + return "5.1.2. [TC-EEM-2.1] Attributes with Server as DUT" + + def pics_TC_EEM_2_1(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["EEM.S"] + + def steps_TC_EEM_2_1(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads Accuracy attribute. Verify that the DUT response contains a MeasurementAccuracyStruct value."), + TestStep("3", "TH reads CumulativeEnergyImported attribute. Verify that the DUT response contains either null or an EnergyMeasurementStruct value."), + TestStep("4", "TH reads CumulativeEnergyExported attribute. Verify that the DUT response contains either null or an EnergyMeasurementStruct value."), + TestStep("5", "TH reads PeriodicEnergyImported attribute. Verify that the DUT response contains either null or an EnergyMeasurementStruct value."), + TestStep("6", "TH reads PeriodicEnergyExported attribute. Verify that the DUT response contains either null or an EnergyMeasurementStruct value."), + TestStep("7", "TH reads CumulativeEnergyReset attribute. Verify that the DUT response contains either null or an CumulativeEnergyResetStruct value."), + ] + + return steps + + @async_test_body + async def test_TC_EEM_2_1(self): + + self.step("1") + # Commission DUT - already done + + self.step("2") + accuracy = await self.read_eem_attribute_expect_success("Accuracy") + logger.info(f"Rx'd Accuracy: {accuracy}") + asserts.assert_not_equal(accuracy, NullValue, "Accuracy is not allowed to be null") + asserts.assert_equal(accuracy.measurementType, Clusters.ElectricalEnergyMeasurement.Enums.MeasurementTypeEnum.kElectricalEnergy, + "Accuracy measurementType must be ElectricalEnergy") + + self.step("3") + cumulativeEnergyImported = await self.read_eem_attribute_expect_success("CumulativeEnergyImported") + logger.info(f"Rx'd CumulativeEnergyImported: {cumulativeEnergyImported}") + + self.step("4") + cumulativeEnergyExported = await self.read_eem_attribute_expect_success("CumulativeEnergyExported") + logger.info(f"Rx'd CumulativeEnergyExported: {cumulativeEnergyExported}") + + self.step("5") + periodicEnergyImported = await self.read_eem_attribute_expect_success("PeriodicEnergyImported") + logger.info(f"Rx'd PeriodicEnergyImported: {periodicEnergyImported}") + + self.step("6") + periodicEnergyExported = await self.read_eem_attribute_expect_success("PeriodicEnergyExported") + logger.info(f"Rx'd PeriodicEnergyExported: {periodicEnergyExported}") + + self.step("7") + cumulativeEnergyReset = await self.read_eem_attribute_expect_success("CumulativeEnergyReset") + logger.info(f"Rx'd CumulativeEnergyReset: {cumulativeEnergyReset}") + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EEM_2_2.py b/src/python_testing/TC_EEM_2_2.py new file mode 100644 index 00000000000000..58e651de9425eb --- /dev/null +++ b/src/python_testing/TC_EEM_2_2.py @@ -0,0 +1,80 @@ +# +# 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. + + +import time + +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EnergyReporting_Utils import EnergyReportingBaseTestHelper + + +class TC_EEM_2_2(MatterBaseTest, EnergyReportingBaseTestHelper): + + def desc_TC_EEM_2_2(self) -> str: + """Returns a description of this test""" + return "5.1.3. [TC-EEM-2.2] Optional cumulative imported energy attributes with DUT as Server" + + def pics_TC_EEM_2_2(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["EEM.S", "EEM.S.F02(CUME)", "EEM.S.F00(IMPE)"] + + def steps_TC_EEM_2_2(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEM.TEST_EVENT_TRIGGER for Start Fake Load Test 1kW Event"), + TestStep("4", "Wait 3 seconds"), + TestStep("4a", "TH reads from the DUT the CumulativeEnergyImported attribute. Verify the read is successful and note the value read."), + TestStep("5", "Wait 3 seconds"), + TestStep("5a", "TH reads from the DUT the CumulativeEnergyImported attribute. Verify the read is successful and that the value is greater than the value measured in step 4a."), + TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEM.TEST_EVENT_TRIGGER for Stop Fake Readings Test Event."), + ] + + return steps + + @async_test_body + async def test_TC_EEM_2_2(self): + + self.step("1") + # Commission DUT - already done + + self.step("2") + await self.check_test_event_triggers_enabled() + + self.step("3") + await self.send_test_event_trigger_start_fake_1kw_load_2s() + + self.step("4") + time.sleep(3) + + self.step("4a") + cumulative_energy_imported = await self.read_eem_attribute_expect_success("CumulativeEnergyImported") + + self.step("5") + time.sleep(3) + + self.step("5a") + cumulative_energy_imported_2 = await self.read_eem_attribute_expect_success("CumulativeEnergyImported") + asserts.assert_greater(cumulative_energy_imported_2.energy, cumulative_energy_imported.energy, + f"Expected cumulative energy readings {cumulative_energy_imported_2.energy} > {cumulative_energy_imported.energy}") + + self.step("6") + await self.send_test_event_trigger_stop_fake_readings() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EEM_2_3.py b/src/python_testing/TC_EEM_2_3.py new file mode 100644 index 00000000000000..1183d9591bcd5e --- /dev/null +++ b/src/python_testing/TC_EEM_2_3.py @@ -0,0 +1,80 @@ +# +# 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. + + +import time + +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EnergyReporting_Utils import EnergyReportingBaseTestHelper + + +class TC_EEM_2_3(MatterBaseTest, EnergyReportingBaseTestHelper): + + def desc_TC_EEM_2_3(self) -> str: + """Returns a description of this test""" + return "5.1.4. [TC-EEM-2.3] Optional cumulative exported energy attributes with DUT as Server" + + def pics_TC_EEM_2_3(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["EEM.S", "EEM.S.F02(CUME)", "EEM.S.F01(EXPE)"] + + def steps_TC_EEM_2_3(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEM.TEST_EVENT_TRIGGER for Start Fake Generator Test 3kW Event"), + TestStep("4", "Wait 6 seconds"), + TestStep("4a", "TH reads from the DUT the CumulativeEnergyExported attribute. Verify the read is successful and note the value read."), + TestStep("5", "Wait 6 seconds"), + TestStep("5a", "TH reads from the DUT the CumulativeEnergyExported attribute. Verify the read is successful and that the value is greater than the value measured in step 4a."), + TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEM.TEST_EVENT_TRIGGER for Stop Fake Readings Test Event."), + ] + + return steps + + @async_test_body + async def test_TC_EEM_2_3(self): + + self.step("1") + # Commission DUT - already done + + self.step("2") + await self.check_test_event_triggers_enabled() + + self.step("3") + await self.send_test_event_trigger_start_fake_3kw_generator_5s() + + self.step("4") + time.sleep(6) + + self.step("4a") + cumulative_energy_exported = await self.read_eem_attribute_expect_success("CumulativeEnergyExported") + + self.step("5") + time.sleep(6) + + self.step("5a") + cumulative_energy_exported_2 = await self.read_eem_attribute_expect_success("CumulativeEnergyExported") + asserts.assert_greater(cumulative_energy_exported_2.energy, cumulative_energy_exported.energy, + f"Expected cumulative energy readings {cumulative_energy_exported_2.energy} > {cumulative_energy_exported.energy}") + + self.step("6") + await self.send_test_event_trigger_stop_fake_readings() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EEM_2_4.py b/src/python_testing/TC_EEM_2_4.py new file mode 100644 index 00000000000000..b219e3c4e770a5 --- /dev/null +++ b/src/python_testing/TC_EEM_2_4.py @@ -0,0 +1,80 @@ +# +# 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. + + +import time + +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EnergyReporting_Utils import EnergyReportingBaseTestHelper + + +class TC_EEM_2_4(MatterBaseTest, EnergyReportingBaseTestHelper): + + def desc_TC_EEM_2_4(self) -> str: + """Returns a description of this test""" + return "5.1.5. [TC-EEM-2.4] Optional periodic imported energy attributes with DUT as Server" + + def pics_TC_EEM_2_4(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["EEM.S", "EEM.S.F03(PERE)", "EEM.S.F00(IMPE)"] + + def steps_TC_EEM_2_4(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEM.TEST_EVENT_TRIGGER for Start Fake Load Test 1kW Event"), + TestStep("4", "Wait 3 seconds"), + TestStep("4a", "TH reads from the DUT the PeriodicEnergyImported attribute. Verify the read is successful and note the value read."), + TestStep("5", "Wait 3 seconds"), + TestStep("5a", "TH reads from the DUT the PeriodicEnergyImported attribute. Verify the read is successful and that the value read has to be different from value measure in step 4a."), + TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEM.TEST_EVENT_TRIGGER for Stop Fake Readings Test Event."), + ] + + return steps + + @async_test_body + async def test_TC_EEM_2_4(self): + + self.step("1") + # Commission DUT - already done + + self.step("2") + await self.check_test_event_triggers_enabled() + + self.step("3") + await self.send_test_event_trigger_start_fake_1kw_load_2s() + + self.step("4") + time.sleep(3) + + self.step("4a") + periodic_energy_imported = await self.read_eem_attribute_expect_success("PeriodicEnergyImported") + + self.step("5") + time.sleep(3) + + self.step("5a") + periodic_energy_imported_2 = await self.read_eem_attribute_expect_success("PeriodicEnergyImported") + asserts.assert_not_equal(periodic_energy_imported_2.energy, periodic_energy_imported.energy, + f"Expected different periodic energy readings {periodic_energy_imported_2.energy} to be != {periodic_energy_imported.energy}") + + self.step("6") + await self.send_test_event_trigger_stop_fake_readings() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EEM_2_5.py b/src/python_testing/TC_EEM_2_5.py new file mode 100644 index 00000000000000..945a97f89da8aa --- /dev/null +++ b/src/python_testing/TC_EEM_2_5.py @@ -0,0 +1,80 @@ +# +# 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. + + +import time + +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EnergyReporting_Utils import EnergyReportingBaseTestHelper + + +class TC_EEM_2_5(MatterBaseTest, EnergyReportingBaseTestHelper): + + def desc_TC_EEM_2_5(self) -> str: + """Returns a description of this test""" + return "5.1.6. [TC-EEM-2.5] Optional periodic exported energy attributes with DUT as Server" + + def pics_TC_EEM_2_5(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["EEM.S", "EEM.S.F03(PERE)", "EEM.S.F01(EXPE)"] + + def steps_TC_EEM_2_5(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEM.TEST_EVENT_TRIGGER for Start Fake Generator Test 3kW Event"), + TestStep("4", "Wait 6 seconds"), + TestStep("4a", "TH reads from the DUT the PeriodicEnergyExported attribute. Verify the read is successful and note the value read."), + TestStep("5", "Wait 6 seconds"), + TestStep("5a", "TH reads from the DUT the PeriodicEnergyExported attribute. Verify the read is successful and that the value read has to be different from value measure in step 4a."), + TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEM.TEST_EVENT_TRIGGER for Stop Fake Readings Test Event."), + ] + + return steps + + @async_test_body + async def test_TC_EEM_2_5(self): + + self.step("1") + # Commission DUT - already done + + self.step("2") + await self.check_test_event_triggers_enabled() + + self.step("3") + await self.send_test_event_trigger_start_fake_3kw_generator_5s() + + self.step("4") + time.sleep(6) + + self.step("4a") + periodic_energy_exported = await self.read_eem_attribute_expect_success("PeriodicEnergyExported") + + self.step("5") + time.sleep(6) + + self.step("5a") + periodic_energy_exported_2 = await self.read_eem_attribute_expect_success("PeriodicEnergyExported") + asserts.assert_not_equal(periodic_energy_exported_2.energy, periodic_energy_exported.energy, + f"Expected different periodic energy readings {periodic_energy_exported_2.energy} to be != {periodic_energy_exported.energy}") + + self.step("6") + await self.send_test_event_trigger_stop_fake_readings() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EPM_2_1.py b/src/python_testing/TC_EPM_2_1.py new file mode 100644 index 00000000000000..fc4347e1ec0a0d --- /dev/null +++ b/src/python_testing/TC_EPM_2_1.py @@ -0,0 +1,211 @@ +# +# 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. + +import logging + +import chip.clusters as Clusters +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EnergyReporting_Utils import EnergyReportingBaseTestHelper + +logger = logging.getLogger(__name__) + + +class TC_EPM_2_1(MatterBaseTest, EnergyReportingBaseTestHelper): + + def desc_TC_EPM_2_1(self) -> str: + """Returns a description of this test""" + return "5.1.2. [TC-EPM-2.1] Attributes with Server as DUT" + + def pics_TC_EPM_2_1(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + return ["EPM.S"] + + def steps_TC_EPM_2_1(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads PowerMode attribute. Verify that the DUT response contains an enum8 value"), + TestStep("3", "TH reads NumberOfMeasurementTypes attribute. Verify that the DUT response contains an uint8 value."), + TestStep("4", "TH reads Accuracy attribute. Verify that the DUT response contains a list of MeasurementAccuracyStruct entries - Verify that the list has between 1 and NumberOfMeasurementTypes entries."), + TestStep("5", "TH reads Ranges attribute. Verify that the DUT response contains a list of MeasurementRangeStruct entries - Verify that the list has between 0 and NumberOfMeasurementTypes entries."), + TestStep("6", "TH reads Voltage attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("7", "TH reads ActiveCurrent attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("8", "TH reads ReactiveCurrent attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("9", "TH reads ApparentCurrent attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of 0 to 2^62."), + TestStep("10", "TH reads ActivePower attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("11", "TH reads ReactivePower attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("12", "TH reads ApparentPower attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("13", "TH reads RMSVoltage attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("14", "TH reads RMSCurrent attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("15", "TH reads RMSPower attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + TestStep("16", "TH reads Frequency attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of 0 to 1000000."), + TestStep("17", "TH reads HarmonicCurrents attribute. Verify that the DUT response contains a list of HarmonicMeasurementStruct entries."), + TestStep("18", "TH reads HarmonicPhases attribute. Verify that the DUT response contains a list of HarmonicMeasurementStruct entries."), + TestStep("19", "TH reads PowerFactor attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -10000 to 10000."), + TestStep("20", "TH reads NeutralCurrent attribute. Verify that the DUT response contains either null or an int64 value. Value has to be between a range of -2^62 to 2^62."), + ] + + return steps + + @async_test_body + async def test_TC_EPM_2_1(self): + + self.step("1") + # Commission DUT - already done + + supported_attributes = await self.get_supported_epm_attributes() + + self.step("2") + power_mode = await self.read_epm_attribute_expect_success("PowerMode") + logger.info(f"Rx'd PowerMode: {power_mode}") + asserts.assert_not_equal(power_mode, Clusters.ElectricalPowerMeasurement.Enums.PowerModeEnum.kUnknown, + "PowerMode must not be Unknown") + + self.step("3") + number_of_measurements = await self.read_epm_attribute_expect_success("NumberOfMeasurementTypes") + logger.info(f"Rx'd NumberOfMeasurementTypes: {number_of_measurements}") + asserts.assert_greater_equal(number_of_measurements, 1, + "NumberOfMeasurementTypes must be >= 1") + + self.step("4") + accuracy = await self.read_epm_attribute_expect_success("Accuracy") + logger.info(f"Rx'd Accuracy: {accuracy}") + logger.info("Checking Accuracy meets spec requirements") + found_active_power = False + for measurement in accuracy: + logging.info( + f"measurementType:{measurement.measurementType} measured:{measurement.measured} minMeasuredValue:{measurement.minMeasuredValue} maxMeasuredValue:{measurement.maxMeasuredValue}") + + # Scan all measurement types to check we have the mandatory kActivePower + if (measurement.measurementType == Clusters.ElectricalPowerMeasurement.Enums.MeasurementTypeEnum.kActivePower): + found_active_power = True + + # Check that the ranges are in order from minimum to maximum and don't have gaps + asserts.assert_equal(measurement.minMeasuredValue, measurement.accuracyRanges[0].rangeMin, + "minMeasuredValue must be the same as 1st accuracyRange rangeMin") + + for index, range_entry in enumerate(measurement.accuracyRanges): + logging.info(f" [{index}] rangeMin:{range_entry.rangeMin} rangeMax:{range_entry.rangeMax} percentMax:{range_entry.percentMax} percentMin:{range_entry.percentMin} percentTypical:{range_entry.percentTypical} fixedMax:{range_entry.fixedMax} fixedMin:{range_entry.fixedMin} fixedTypical:{range_entry.fixedTypical}") + asserts.assert_greater(range_entry.rangeMax, range_entry.rangeMin, "rangeMax should be > rangeMin") + if index == 0: + minimum_range = range_entry.rangeMin + maximum_range = range_entry.rangeMax + prev_range_max = range_entry.rangeMax + else: + minimum_range = min(minimum_range, range_entry.rangeMin) + maximum_range = max(maximum_range, range_entry.rangeMax) + asserts.assert_equal(range_entry.rangeMin, prev_range_max + 1, + f"Index[{index}] rangeMin was not +1 more then previous index's rangeMax {prev_range_max}") + prev_range_max = range_entry.rangeMax + + # Check that the last range rangeMax has the same value as the measurement.maxMeasuredValue + asserts.assert_equal(measurement.maxMeasuredValue, prev_range_max, + "maxMeasuredValue must be the same as the last accuracyRange rangeMax") + asserts.assert_equal(maximum_range, measurement.maxMeasuredValue, + "The maxMeasuredValue must be the same as any of the maximum of all rangeMax's") + asserts.assert_equal(minimum_range, measurement.minMeasuredValue, + "The minMeasuredValue must be the same as any of the minimum of all rangeMin's") + + asserts.assert_is(found_active_power, True, "There must be an ActivePower measurement accuracy") + asserts.assert_equal(len(accuracy), number_of_measurements, + "The number of accuracy entries should match the NumberOfMeasurementTypes") + + self.step("5") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.Ranges.attribute_id in supported_attributes): + ranges = await self.read_epm_attribute_expect_success("Ranges") + logger.info(f"Rx'd Ranges: {ranges}") + # Check list length between 0 and NumberOfMeasurementTypes + asserts.assert_greater_equal(len(ranges), 0) + asserts.assert_less_equal(len(ranges), number_of_measurements) + + self.step("6") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.Voltage.attribute_id in supported_attributes): + voltage = await self.check_epm_attribute_in_range("Voltage", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd Voltage: {voltage}") + + self.step("7") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.ActiveCurrent.attribute_id in supported_attributes): + active_current = await self.check_epm_attribute_in_range("ActiveCurrent", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd ActiveCurrent: {active_current}") + + self.step("8") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.ReactiveCurrent.attribute_id in supported_attributes): + reactive_current = await self.check_epm_attribute_in_range("ReactiveCurrent", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd ReactiveCurrent: {reactive_current}") + + self.step("9") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.ApparentCurrent.attribute_id in supported_attributes): + apparent_current = await self.check_epm_attribute_in_range("ApparentCurrent", 0, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd ApparentCurrent: {apparent_current}") + + self.step("10") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.ActivePower.attribute_id in supported_attributes): + active_power = await self.check_epm_attribute_in_range("ActivePower", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd ActivePower: {active_power}") + + self.step("11") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.ReactivePower.attribute_id in supported_attributes): + reactive_power = await self.check_epm_attribute_in_range("ReactivePower", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd ReactivePower: {reactive_power}") + + self.step("12") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.ApparentPower.attribute_id in supported_attributes): + apparent_power = await self.check_epm_attribute_in_range("ApparentPower", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd ApparentPower: {apparent_power}") + + self.step("13") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.RMSVoltage.attribute_id in supported_attributes): + rms_voltage = await self.check_epm_attribute_in_range("RMSVoltage", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd RMSVoltage: {rms_voltage}") + + self.step("14") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.RMSCurrent.attribute_id in supported_attributes): + rms_current = await self.check_epm_attribute_in_range("RMSCurrent", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd RMSCurrent: {rms_current}") + + self.step("15") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.RMSPower.attribute_id in supported_attributes): + rms_power = await self.check_epm_attribute_in_range("RMSPower", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd RMSPower: {rms_power}") + + self.step("16") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.Frequency.attribute_id in supported_attributes): + frequency = await self.check_epm_attribute_in_range("Frequency", 0, 1000000, allow_null=True) + logger.info(f"Rx'd Frequency: {frequency}") + + self.step("17") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.HarmonicCurrents.attribute_id in supported_attributes): + harmonic_currents = await self.read_epm_attribute_expect_success("HarmonicCurrents", allow_null=True) + logger.info(f"Rx'd HarmonicCurrents: {harmonic_currents}") + + self.step("18") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.HarmonicPhases.attribute_id in supported_attributes): + harmonic_phases = await self.read_epm_attribute_expect_success("HarmonicPhases", allow_null=True) + logger.info(f"Rx'd HarmonicPhases: {harmonic_phases}") + + self.step("19") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.PowerFactor.attribute_id in supported_attributes): + power_factor = await self.check_epm_attribute_in_range("PowerFactor", -10000, 10000, allow_null=True) + logger.info(f"Rx'd PowerFactor: {power_factor}") + + self.step("20") + if self.pics_guard(Clusters.ElectricalPowerMeasurement.Attributes.NeutralCurrent.attribute_id in supported_attributes): + neutral_current = await self.check_epm_attribute_in_range("NeutralCurrent", -2 ^ 62, 2 ^ 62, allow_null=True) + logger.info(f"Rx'd NeutralCurrent: {neutral_current}") + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EPM_2_2.py b/src/python_testing/TC_EPM_2_2.py new file mode 100644 index 00000000000000..eb6f6081f6d690 --- /dev/null +++ b/src/python_testing/TC_EPM_2_2.py @@ -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. + + +import logging +import time + +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EnergyReporting_Utils import EnergyReportingBaseTestHelper + +logger = logging.getLogger(__name__) + + +class TC_EPM_2_2(MatterBaseTest, EnergyReportingBaseTestHelper): + + def desc_TC_EPM_2_2(self) -> str: + """Returns a description of this test""" + return "5.1.3. [TC-EPM-2.2] Primary functionality with DUT as Server" + + def pics_TC_EPM_2_2(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + # In this case - there is no feature flags needed to run this test case + return ["EPM.S"] + + def steps_TC_EPM_2_2(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EPM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EPM.TEST_EVENT_TRIGGER for Start Fake Load Test 1kW Event"), + TestStep("4", "Wait 3 seconds"), + TestStep("4a", "TH reads from the DUT the ActivePower attribute. Verify the read is successful and that the value is between 980'000 and 1'020'000 mW. Note the value read."), + TestStep("4b", "TH reads from the DUT the ActiveCurrent attribute. Verify the read is successful and that the value is between 3'848 and 4'848 mA. Note the value read."), + TestStep("4c", "TH reads from the DUT the Voltage attribute. Verify the read is successful and that the value is between 229'000 and 231'000 mV. Note the value read."), + TestStep("5", "Wait 3 seconds"), + TestStep("5a", "TH reads from the DUT the ActivePower attribute. Verify the read is successful, that the value is between '980'000 and 1'020'000 mW, and the value is different from the value read in step 4a."), + TestStep("5b", "TH reads from the DUT the ActiveCurrent attribute. Verify the read is successful, that the value is between 3'848 and 4'848 mA, and the value is different from the value read in step 4b."), + TestStep("5c", "TH reads from the DUT the Voltage attribute. Verify the read is successful, that the value is between 229'000 and 231'000 mV, and the value is different from the value read in step 4c."), + TestStep("6", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EPM.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EPM.TEST_EVENT_TRIGGER for Stop Fake Readings Test Event."), + ] + + return steps + + @async_test_body + async def test_TC_EPM_2_2(self): + + self.step("1") + # Commission DUT - already done + + self.step("2") + await self.check_test_event_triggers_enabled() + + self.step("3") + await self.send_test_event_trigger_start_fake_1kw_load_2s() + + # After 3 seconds... + self.step("4") + time.sleep(3) + + self.step("4a") + active_power = await self.check_epm_attribute_in_range("ActivePower", 980000, 1020000) # 1kW +/- 20W + + self.step("4b") + active_current = await self.check_epm_attribute_in_range("ActiveCurrent", 3848, 4848) # 4.348 A +/- 500mA + + self.step("4c") + voltage = await self.check_epm_attribute_in_range("Voltage", 229000, 231000) # 230V +/- 1V + + self.step("5") + # After 3 seconds... + time.sleep(3) + + self.step("5a") + active_power2 = await self.check_epm_attribute_in_range("ActivePower", 980000, 1020000) # 1kW +/- 20W + asserts.assert_not_equal(active_power, active_power2, + f"Expected ActivePower readings to have changed {active_power}, {active_power2}") + + self.step("5b") + active_current2 = await self.check_epm_attribute_in_range("ActiveCurrent", 3848, 4848) # 4.348 A +/- 500mA + asserts.assert_not_equal(active_current, active_current2, + f"Expected ActiveCurrent readings to have changed {active_current}, {active_current2}") + + self.step("5c") + voltage2 = await self.check_epm_attribute_in_range("Voltage", 229000, 231000) # 230V +/- 1V + asserts.assert_not_equal(voltage, voltage2, f"Expected Voltage readings to have changed {voltage}, {voltage2}") + + self.step("6") + await self.send_test_event_trigger_stop_fake_readings() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EnergyReporting_Utils.py b/src/python_testing/TC_EnergyReporting_Utils.py new file mode 100644 index 00000000000000..4f26254d61a2de --- /dev/null +++ b/src/python_testing/TC_EnergyReporting_Utils.py @@ -0,0 +1,78 @@ +# +# 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. + + +import logging + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from mobly import asserts + +logger = logging.getLogger(__name__) + + +class EnergyReportingBaseTestHelper: + + async def read_epm_attribute_expect_success(self, attribute: str = "", endpoint: int = None, ): + cluster = Clusters.Objects.ElectricalPowerMeasurement + full_attr = getattr(cluster.Attributes, attribute) + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + async def read_eem_attribute_expect_success(self, attribute: str = "", endpoint: int = None): + cluster = Clusters.Objects.ElectricalEnergyMeasurement + full_attr = getattr(cluster.Attributes, attribute) + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + def check_value_in_range(self, attribute: str, value: int, lower_value: int, upper_value: int): + asserts.assert_greater_equal(value, lower_value, + f"Unexpected '{attribute}' value - expected {lower_value}, was {value}") + asserts.assert_less_equal(value, upper_value, + f"Unexpected '{attribute}' value - expected {upper_value}, was {value}") + + async def check_epm_attribute_in_range(self, attribute, lower_value: int, upper_value: int, endpoint: int = None, allow_null: bool = False): + value = await self.read_epm_attribute_expect_success(endpoint=endpoint, attribute=attribute) + if allow_null and value is NullValue: + # skip the range check + logger.info("value is NULL - OK") + return value + + self.check_value_in_range(attribute, value, lower_value, upper_value) + return value + + async def check_eem_attribute_in_range(self, attribute, lower_value: int, upper_value: int, endpoint: int = None, allow_null: bool = False): + value = await self.read_eem_attribute_expect_success(endpoint=endpoint, attribute=attribute) + if allow_null and value is NullValue: + # skip the range check + logger.info("value is NULL - OK") + return value + + self.check_value_in_range(attribute, value, lower_value, upper_value) + return value + + async def get_supported_epm_attributes(self, endpoint: int = None): + return await self.read_epm_attribute_expect_success("AttributeList", endpoint) + + async def get_supported_eem_attributes(self, endpoint: int = None): + return await self.read_eem_attribute_expect_success("AttributeList", endpoint) + + async def send_test_event_trigger_stop_fake_readings(self): + await self.send_test_event_triggers(eventTrigger=0x0091000000000000) + + async def send_test_event_trigger_start_fake_1kw_load_2s(self): + await self.send_test_event_triggers(eventTrigger=0x0091000000000001) + + async def send_test_event_trigger_start_fake_3kw_generator_5s(self): + await self.send_test_event_triggers(eventTrigger=0x0091000000000002) diff --git a/src/python_testing/matter_testing_support.py b/src/python_testing/matter_testing_support.py index 2f3ca96d12038d..1515f0310d3302 100644 --- a/src/python_testing/matter_testing_support.py +++ b/src/python_testing/matter_testing_support.py @@ -976,8 +976,21 @@ def on_pass(self, record): self.runner_hook.test_stop(exception=None, duration=test_duration) def pics_guard(self, pics_condition: bool): + """Checks a condition and if False marks the test step as skipped and + returns False, otherwise returns True. + For example can be used to check if a test step should be run: + + self.step("4") + if self.pics_guard(condition_needs_to_be_true_to_execute): + # do the test for step 4 + + self.step("5") + if self.pics_guard(condition2_needs_to_be_true_to_execute): + # do the test for step 5 + """ if not pics_condition: self.mark_current_step_skipped() + return pics_condition def mark_current_step_skipped(self): try: