From 9ef5bbfd520d8847970e59b343ae50a655463749 Mon Sep 17 00:00:00 2001 From: jamesharrow <93921463+jamesharrow@users.noreply.github.com> Date: Fri, 26 Jul 2024 12:24:38 +0100 Subject: [PATCH] EVSE add get set clear targets support (#31407) * De-alphabetize list of files to avoid breaking GH action * Semi-realphabetize? * Restore strangely dropped events * Better BitMask handling * Change min/max on electrical measurements to be decimal instead of hex * Rename meas-and-sense to measurement-and-sensing.xml * Remove seemingly superfluous attribute requirements on Descriptor cluster on Electrical Measurement * Updates to electrical-power-measurement-server based on comments * Remove defaults from MeasurementAccuracyRangeStruct to match spec update * Restore side="server" to events * Move common enums and bitmaps to detail:: instead of detail::Enums and detail::Bitmaps; remove superfluous using statement * Assign ID to Electrical Sensor device type * Removed EPM and EEM from Root Node Device * Restyled formatting is different than clang-format * Re-add FeatureMap to attributeAccessInterfaceAttributes for EEM and EPM * Regen after merge * Added electrical-energy-measurement-server to CMakelist to fix linker issue. * Lock client on Electrical Sensor device type * Remove unneeded using statement now that Enums are in detail:: * Check for null iterators and error * Switch to ResourceExhausted from CHIP_ERROR_INTERNAL * Re-enabled EEM in energy management app and regen all after previous merge * Some refactoring to add EPM Instance into the EVSEManufacturer class to clean up containment. Added ability to fake voltage, power and current to the TE triggers. * Missed one file. * Fixed crash due to unassigned dg pointer. Power/Voltage/Current faking working too. * Touch file since restyled crashed * Restyled by gn * Restyled by isort * Add stub for EPM cluster * Reverted whitespace change * Did regen_all after merge from master to resolve conflicts. * Put back line of clusters which somehow got deleted accidentally. * Remerged ZAP file and regen all after resolving conflicts. * Fixes for Python tests * Correct name for Ember init callback * Formatting * Sync optional attributes list with .zap file for EPM * Add missing features to EPM stub * Revert FeatureMap in attributeAccessInterfaceAttributes * Allow FeatureMap in EEM constructor; add all-clusters-app EEM stub * Forgot zcl-with-test-extensions * Unregister EEM attribute access in destructor * Remove redundant returns to keep clang-tidy happy * Fix for issue mentioned in code review on EEM cluster limiting the number of endpoints it allows. * Refactoring to have a common EvseMain across all platforms to avoid making changes in multiple places * Added electrical-power-measurement-server to ESP32 CMakeLists.txt * Updated Matter device types to add EVSE * Open and saved energy-management-app.zap and regen_all * Removed duplicate ElectricalEnergyMeasurment class which was accidentally merged in. Fixed issue raised about ElectricalEnergyMeasurement array size not working on bridges. * Added support for test event triggers and handling of reading events into matter_testing_support. * Made TC_EEVSE_Utils.py use the matter_testing_support instead of its own local copy. * Restyled by isort * Cherry pick from Tweaks to EVSE Test plans (Issue #31460) * Changed the random value generation to make the values +/- and handle sign conversion to avoid compile warnings * Enabled cumulativeEnergyExported in Energy-management-app. * Added initial electrical power measurement 2.2 test case * Changed copyright date * Code review comment fixes. * Changed to c++ style cast * Fixed trailing whitepace * Added support for testing read of EEM attributes and change of values * Corrected EPM references in TC_EEM_2_2. Added TC_EEM_2_3 * Added periodic energy reporting, and new cumulativeEnergyReset attribute into energy-management-app.zap * Added periodic energy reading support and TC_EEM_2_3 to 2_5. * Python removed unused logging and EventChangeCallback * Updates to align to test plan PR #3949 * Added initial EEM_2_1 test script. * Added example of setting EEM Accuracy and EEM CumulativeEnergyReset structure - TC_EEM_2_1 now passes * Restyled by whitespace * Restyled * Removed extra spaces in TC_EEM_2_1.py * Removed unused EnergyManagementManager.cpp/.h * Fixed PowerMode = kAc * Initial TC_EPM_2_1.py script * Restyled by isort * Merged TC_EEVSE tests back in * Initialized NumberOfMeasurementTypes * Added EEM 2.1,2.2,2.3,2.4,2.5 and EPM 2.1,2.2 into CI workflow tests.yaml * Interim state - partially refactored how Measurement Structs are encoded similar to how ModeBase clusters are implemented. Needs tidy up. Will break all-clusters for now * Removed SetNumberOfMeasurementTypes since this can be derived from the ArraySize(kMeasurementAccuracies). Added more stringent checking in test script of measurementTypes and ranges. * Completed TC_EPM_2_1.py script * Corrected test plan spec reference. * Test EPM_2_1 now runs and passes. Allows checking that attributes are supported, and skips test if not. Validation of values ignores Nulls (which are allowed). Turned on Ranges attribute. * Revert unintended change to tests.yaml * Python test case code-review updates * Removed old range iterator. * Fixed lint issues and adjusted timings to match the test plan pr. * Fixed all-clusters electrical-power-measurement cluster by using the energy-management-app/common Delegate * Implemented HarmonicCurrents and HarmonicStructs (to return empty list for now) * Updated TC_EEVSE_2_3.py from more up to date branch. * Added basic set/get/clear targets framework based on earlier work. Not working * Removed files that were recently deleted upstream * Corrected PICS in TC_EEVSE_2_3.py and copyright date * Interim checkin with GetTargets not working * Initial GetTargets working with a constant static array to demonstrate the command response can be encoded ok * Removed unused EnergyEvseManager.cpp, Added EnergyEvseTargetStore.cpp * Added EVSETargets to DefaultStorageKeyAllocator.h * Reverted incorrect removal of EnergyEvseManager.cpp * Added override into EnergyEvseDelegate which resulted in strange errors on some platforms. * Added basic infrastructure for storing targets and reading them back. Compiles and runs * Store working * Store and Load seem to work. * Fixed Status change caused by upstream changes * Added helper function to compute day of week bitmap * Fix to evse-stub in all-clusters to add gEvseTargetsDelegate * Fixed build error on some platforms: cast of dayOfWeek to uint8_t * Refactoring of GetTargets command to build the response using dynamic memory * Updated evse-server.cpp/.h * Tidy up of unused CommonIterator in electrical-power-measurement-server.h * Get Targets reading back from file working * Starting to clean up - ran through Valgrind to check for memory leaks. * More clean up * Added Clear Targets support and initial implementation of SetTargets * Fixed crash when erasing entries - test reported PASS! * Attempt to fix Darwin complaint about unsigned int cast * Added logging of get targets response * Fixed platform specific logging compilation issue * Clean up of unused code * More clean up * Removed unused variable - Darwin check * clang checker updates * Refactored code to fix missing added energy since the TargetSoC could be optional and calling Next() would skip AddedEnergy * Added checking of GetTargetsResponse - PASSES * Almost working but need to resolve the Charging current and start time calculation in some scenarios. * Fixed PEP8 lint errors in TC_EVSE_2_3.py and TC_EEVSE_Utils.py taking advantage of new TestStep 3rd arg * Restyled by isort * Fixed ChipLogDetail %d errors on some platforms with a static_cast * Fixes in EVSE FindTargets to remove signed/unsigned comparison. Calculation of charging time was out by factor of 10. * Fixed TC_EEVSE_2_3.py to match test plan. Fixed EVSE FindNextTarget to handle targets if searching for future days. Now passes tests. * Fixed FindNextTarget to use epoch_s for NextChargeTargetTime and NextChargeStartTime with associated TC_EEVSE_2_3.py changes. Fixed bug when TargetSoC = 100% caused start time to be reported incorrectly. * Fix: When EVSE is not plugged in, or not enabled for charge we shouldn't compute a schedule. Also add callbacks to update schedule if the state changes. * Updated TC_EEM, TC_EPM, TC_EEVSE to take advantage of the new TestStep 3rd element. * Address comments from Boris Zbarsky * Fix lint error by adding entry for src/app/clusters/energy-evse-server/energy-evse-server.h * Revert "Fix lint error by adding entry for src/app/clusters/energy-evse-server/energy-evse-server.h" This reverts commit 7a60876809ce0af89a9edb00909122a27f922a5e. * Rework HandleGetTargets following https://github.com/project-chip/connectedhomeip/pull/33736 * Address comments from Boris Zbarsky * Update TC_EEVSE_2_3 to align with latest test spec * Remove the use of vector * Get all targets building again * Catch up from master + fix some lint and build errors * Restyled by whitespace * Restyled by clang-format * Restyled by autopep8 * Restyled by isort * Fixing regession-build error * Restyled by clang-format * Fixing regession-build error * Changed logic to not return NextChargeRequiredEnergy if not plugged in or not enabled for charging. * Removed max 80A current limit which aligns to the spec. Who knows how many Amps chargers will have in the future! * Bug fix - When disabling the mMaximumChargingCurrentLimitFromCommand was not being updated, so we didn't get attribute updates for MaximumChargeCurrent when toggling Enable-Disable-Enable. * Being into line with latest EVSE test spec although TODOs around the time of use trigger * Load the targets in EnergyEvseInit() * Made EvseTargetsEntryType -> evseTargetsEntryType (since it is a variable not a type) * Reformatting and correcting the cluster PICS to be EEVSE not DEM. * Reverted changes to python test cases which don't belong in this PR - See PR#34243 (makes this PR 10 files smaller). * Address review comments from James Harrow * Address review comments from James Harrow * Add energy unit tests * Fix test case to align with PR10027 * Move the EVSE targets key to be a private member of EnergyEvseTargetsStore * Move EvseTargetStore test harness to examples/energy-management-app/energy-management-common/tests * Move example tests to examples/BUILD.gn * Restyled by clang-format * Restyled by gn * Add a mechanism to enable the targets persistent data to be delete when the last fabric is deleted * Fix unused variable compilation issues * Restyled by whitespace * Restyled by clang-format * Adde TC_EEVSE_2_3.py to CI testing. * Added new test-runner info to top of Python TC_EEVSE_2_3.py * Remove assert from BUILD.gn so test harness can build * Put BEGIN CI TEST ARGUMENTS around runner test arguments * Fix compilation problem in unit test * Try and fix the zephyr build problems * Restyled by gn * Try and fix the zephyr build problems * Try and fix the zephyr build problems * Try and fix the zephyr build problems * Restyled by gn * Start addressing comments from Andrei * Continuing addressing comments from Andrei * Continuing addressing comments from Andrei * Restyled by clang-format * Fix a problem with the return code spotted by Boris * Update examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp Co-authored-by: Andrei Litvin * Update examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp Co-authored-by: Andrei Litvin * Update examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp Co-authored-by: Andrei Litvin * Update with further review comments from Andrei * Update with further review comments from Andrei * Update with further review comments from Andrei * Get all targets building * Restyled by whitespace * Restyled by clang-format * Update with further review comments from Andrei * Address further review comments from Andrei * Restyled by clang-format * Complete rename of Reset to ChargingTargetsMemMgr::PrepareDaySchedule * Fix potential memory leak * Add check in ChargingTargetsMemMgr::PrepareDaySchedule against a bad chargingTargetSchedulesIdx * Restyled by clang-format * Address final comments from Andrei * Restyled by gn * Removed whitespace in openiotsdk/CMakelists.txt * Fix merge issue * Fix CI build issue --------- Co-authored-by: Hasty Granbery Co-authored-by: Restyled.io Co-authored-by: pcoleman Co-authored-by: PeterC1965 <101805108+PeterC1965@users.noreply.github.com> Co-authored-by: Andrei Litvin --- .github/workflows/tests.yaml | 1 + BUILD.gn | 2 + examples/BUILD.gn | 32 ++ .../src/energy-evse-stub.cpp | 12 +- .../all-clusters-app/ameba/chip_main.cmake | 4 +- examples/all-clusters-app/asr/BUILD.gn | 2 + .../all-clusters-app/cc13x2x7_26x2x7/BUILD.gn | 0 .../all-clusters-app/cc13x4_26x4/BUILD.gn | 2 + .../all-clusters-app/infineon/psoc6/BUILD.gn | 2 + examples/all-clusters-app/linux/BUILD.gn | 2 + examples/all-clusters-app/mbed/CMakeLists.txt | 4 +- .../nrfconnect/CMakeLists.txt | 4 +- examples/all-clusters-app/nxp/mw320/BUILD.gn | 2 + .../openiotsdk/CMakeLists.txt | 4 +- .../all-clusters-app/telink/CMakeLists.txt | 4 +- examples/all-clusters-app/tizen/BUILD.gn | 2 + .../include/ChargingTargetsMemMgr.h | 156 ++++++ .../include/EVSEManufacturerImpl.h | 6 + .../include/EnergyEvseDelegateImpl.h | 72 ++- .../include/EnergyEvseTargetsStore.h | 123 +++++ .../src/ChargingTargetsMemMgr.cpp | 193 +++++++ .../src/EVSEManufacturerImpl.cpp | 223 +++++++- .../src/EnergyEvseDelegateImpl.cpp | 277 +++++++--- .../src/EnergyEvseEventTriggers.cpp | 17 +- .../src/EnergyEvseMain.cpp | 27 +- .../src/EnergyEvseManager.cpp | 11 + .../src/EnergyEvseTargetsStore.cpp | 489 ++++++++++++++++++ .../energy-management-common/tests/BUILD.gn | 44 ++ .../tests/TestEvseTargetsStorage.cpp | 219 ++++++++ examples/energy-management-app/linux/BUILD.gn | 21 +- examples/energy-management-app/linux/main.cpp | 6 +- examples/platform/linux/BUILD.gn | 8 +- examples/shell/shell_common/BUILD.gn | 2 + src/BUILD.gn | 9 + .../electrical-energy-measurement-server.cpp | 1 + .../electrical-power-measurement-server.h | 3 - .../EnergyEvseTestEventTriggerHandler.h | 4 + .../energy-evse-server/energy-evse-server.cpp | 137 ++++- .../energy-evse-server/energy-evse-server.h | 37 +- src/python_testing/TC_EEVSE_2_3.py | 454 ++++++++++++++++ src/python_testing/TC_EEVSE_Utils.py | 71 ++- 41 files changed, 2568 insertions(+), 121 deletions(-) create mode 100644 examples/BUILD.gn create mode 100644 examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn create mode 100644 examples/energy-management-app/energy-management-common/include/ChargingTargetsMemMgr.h create mode 100644 examples/energy-management-app/energy-management-common/include/EnergyEvseTargetsStore.h create mode 100644 examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp create mode 100644 examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp create mode 100644 examples/energy-management-app/energy-management-common/tests/BUILD.gn create mode 100644 examples/energy-management-app/energy-management-common/tests/TestEvseTargetsStorage.cpp create mode 100644 src/python_testing/TC_EEVSE_2_3.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d17e5db8a2a541..51832814ac548d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -527,6 +527,7 @@ jobs: scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEM_2_4.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEM_2_5.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEVSE_2_2.py' + scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEVSE_2_3.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEVSE_2_4.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EEVSE_2_5.py' scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_EPM_2_1.py' diff --git a/BUILD.gn b/BUILD.gn index ddd893a4ca49d8..ecf710efdf8a15 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -146,6 +146,8 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { if (chip_build_tests) { deps += [ "//src:tests" ] + deps += [ "//examples:example_tests" ] + if (current_os == "android" && current_toolchain == default_toolchain) { deps += [ "${chip_root}/build/chip/java/tests:java_build_test" ] } diff --git a/examples/BUILD.gn b/examples/BUILD.gn new file mode 100644 index 00000000000000..a8cb18422949e2 --- /dev/null +++ b/examples/BUILD.gn @@ -0,0 +1,32 @@ +# Copyright (c) 2020-2021 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("${build_root}/config/compiler/compiler.gni") +import("${chip_root}/build/chip/tests.gni") +import("${chip_root}/src/platform/device.gni") + +if (chip_build_tests) { + import("${chip_root}/build/chip/chip_test_group.gni") + + chip_test_group("example_tests") { + deps = [] + tests = [] + if (chip_device_platform == "linux" && current_os == "linux") { + tests += [ "${chip_root}/examples/energy-management-app/energy-management-common/tests" ] + } + } +} diff --git a/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp b/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp index 72fe588995784e..6acc81ac285b65 100644 --- a/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp +++ b/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp @@ -23,14 +23,24 @@ using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; static std::unique_ptr gDelegate; +static std::unique_ptr gEvseTargetsDelegate; static std::unique_ptr gInstance; void emberAfEnergyEvseClusterInitCallback(chip::EndpointId endpointId) { VerifyOrDie(endpointId == 1); // this cluster is only enabled for endpoint 1. + VerifyOrDie(!gDelegate); + VerifyOrDie(!gEvseTargetsDelegate); VerifyOrDie(!gInstance); - gDelegate = std::make_unique(); + gEvseTargetsDelegate = std::make_unique(); + if (!gEvseTargetsDelegate) + { + ChipLogError(AppServer, "Failed to allocate memory for EvseTargetsDelegate"); + return; + } + + gDelegate = std::make_unique(*gEvseTargetsDelegate); if (gDelegate) { gInstance = std::make_unique( diff --git a/examples/all-clusters-app/ameba/chip_main.cmake b/examples/all-clusters-app/ameba/chip_main.cmake index ce557a2bffaf30..e6e9ee19a87394 100644 --- a/examples/all-clusters-app/ameba/chip_main.cmake +++ b/examples/all-clusters-app/ameba/chip_main.cmake @@ -187,13 +187,15 @@ list( ${chip_dir}/examples/microwave-oven-app/microwave-oven-common/src/microwave-oven-device.cpp - ${chip_dir}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp + ${chip_dir}/examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp ${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/EVSEManufacturerImpl.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 + ${chip_dir}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp + ${chip_dir}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp ${chip_dir}/examples/platform/ameba/route_hook/ameba_route_hook.c ${chip_dir}/examples/platform/ameba/route_hook/ameba_route_table.c diff --git a/examples/all-clusters-app/asr/BUILD.gn b/examples/all-clusters-app/asr/BUILD.gn index 6c9d334a1ed6c2..fc7ad9037b99de 100644 --- a/examples/all-clusters-app/asr/BUILD.gn +++ b/examples/all-clusters-app/asr/BUILD.gn @@ -82,12 +82,14 @@ asr_executable("clusters_app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/smco-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "${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/ChargingTargetsMemMgr.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/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/EnergyEvseManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp", "${examples_plat_dir}/ButtonHandler.cpp", "${examples_plat_dir}/CHIPDeviceManager.cpp", diff --git a/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn b/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/examples/all-clusters-app/cc13x4_26x4/BUILD.gn b/examples/all-clusters-app/cc13x4_26x4/BUILD.gn index 45efe2d45e9bce..c1cb81a9722f7d 100644 --- a/examples/all-clusters-app/cc13x4_26x4/BUILD.gn +++ b/examples/all-clusters-app/cc13x4_26x4/BUILD.gn @@ -72,12 +72,14 @@ ti_simplelink_executable("all-clusters-app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/smco-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "${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/ChargingTargetsMemMgr.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/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/EnergyEvseManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp", "${chip_root}/examples/providers/DeviceInfoProviderImpl.cpp", "${chip_root}/src/app/clusters/general-diagnostics-server/GenericFaultTestEventTriggerHandler.cpp", diff --git a/examples/all-clusters-app/infineon/psoc6/BUILD.gn b/examples/all-clusters-app/infineon/psoc6/BUILD.gn index 2c0b0a6fee7dfa..9be06891342d7c 100644 --- a/examples/all-clusters-app/infineon/psoc6/BUILD.gn +++ b/examples/all-clusters-app/infineon/psoc6/BUILD.gn @@ -124,12 +124,14 @@ psoc6_executable("clusters_app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/smco-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "${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/ChargingTargetsMemMgr.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/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/EnergyEvseManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp", "${examples_plat_dir}/LEDWidget.cpp", "${examples_plat_dir}/init_psoc6Platform.cpp", diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index ca1149b25e7891..3564153a482cca 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -59,12 +59,14 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/tcc-mode.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/water-heater-mode.cpp", "${chip_root}/examples/all-clusters-app/linux/diagnostic-logs-provider-delegate-impl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.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/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/EnergyEvseManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.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", diff --git a/examples/all-clusters-app/mbed/CMakeLists.txt b/examples/all-clusters-app/mbed/CMakeLists.txt index 9b490ff687d1e3..427517aff9f8e1 100644 --- a/examples/all-clusters-app/mbed/CMakeLists.txt +++ b/examples/all-clusters-app/mbed/CMakeLists.txt @@ -72,13 +72,15 @@ 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/EnergyTimeUtils.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/ChargingTargetsMemMgr.cpp ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementManager.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EVSEManufacturerImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/ElectricalPowerMeasurementDelegate.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseManager.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseTargetsStore.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/EnergyTimeUtils.cpp ) chip_configure_data_model(${APP_TARGET} diff --git a/examples/all-clusters-app/nrfconnect/CMakeLists.txt b/examples/all-clusters-app/nrfconnect/CMakeLists.txt index c290003532a431..e11dcbd14356fc 100644 --- a/examples/all-clusters-app/nrfconnect/CMakeLists.txt +++ b/examples/all-clusters-app/nrfconnect/CMakeLists.txt @@ -62,13 +62,15 @@ target_sources(app PRIVATE ${ALL_CLUSTERS_COMMON_DIR}/src/air-quality-instance.cpp ${ALL_CLUSTERS_COMMON_DIR}/src/concentration-measurement-instances.cpp ${ALL_CLUSTERS_COMMON_DIR}/src/resource-monitoring-delegates.cpp - ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyTimeUtils.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/ChargingTargetsMemMgr.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementManager.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EVSEManufacturerImpl.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/EnergyEvseTargetsStore.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyTimeUtils.cpp ${NRFCONNECT_COMMON}/util/LEDWidget.cpp) chip_configure_data_model(app diff --git a/examples/all-clusters-app/nxp/mw320/BUILD.gn b/examples/all-clusters-app/nxp/mw320/BUILD.gn index 83877a175993df..e5e3671143ee99 100644 --- a/examples/all-clusters-app/nxp/mw320/BUILD.gn +++ b/examples/all-clusters-app/nxp/mw320/BUILD.gn @@ -89,12 +89,14 @@ mw320_executable("shell_mw320") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/smco-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "${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/ChargingTargetsMemMgr.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/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/EnergyEvseManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp", "${chip_root}/src/lib/shell/streamer_mw320.cpp", "binding-handler.cpp", diff --git a/examples/all-clusters-app/openiotsdk/CMakeLists.txt b/examples/all-clusters-app/openiotsdk/CMakeLists.txt index 1395cf170a8b79..9db62a3df783a1 100644 --- a/examples/all-clusters-app/openiotsdk/CMakeLists.txt +++ b/examples/all-clusters-app/openiotsdk/CMakeLists.txt @@ -65,13 +65,15 @@ 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/EnergyTimeUtils.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/ChargingTargetsMemMgr.cpp ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementManager.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EVSEManufacturerImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/ElectricalPowerMeasurementDelegate.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseManager.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseTargetsStore.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/EnergyTimeUtils.cpp ) target_link_libraries(${APP_TARGET} diff --git a/examples/all-clusters-app/telink/CMakeLists.txt b/examples/all-clusters-app/telink/CMakeLists.txt index 149b38e3528cce..675ea4d9eb7e74 100644 --- a/examples/all-clusters-app/telink/CMakeLists.txt +++ b/examples/all-clusters-app/telink/CMakeLists.txt @@ -51,13 +51,15 @@ 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/EnergyTimeUtils.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/ChargingTargetsMemMgr.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementManager.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EVSEManufacturerImpl.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/EnergyEvseTargetsStore.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyTimeUtils.cpp ${TELINK_COMMON}/common/src/mainCommon.cpp ${TELINK_COMMON}/common/src/AppTaskCommon.cpp ${TELINK_COMMON}/util/src/LEDManager.cpp diff --git a/examples/all-clusters-app/tizen/BUILD.gn b/examples/all-clusters-app/tizen/BUILD.gn index 9aff82b57b2876..855416c0446923 100644 --- a/examples/all-clusters-app/tizen/BUILD.gn +++ b/examples/all-clusters-app/tizen/BUILD.gn @@ -38,12 +38,14 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/smco-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "${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/ChargingTargetsMemMgr.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/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/EnergyEvseManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp", ] diff --git a/examples/energy-management-app/energy-management-common/include/ChargingTargetsMemMgr.h b/examples/energy-management-app/energy-management-common/include/ChargingTargetsMemMgr.h new file mode 100644 index 00000000000000..66009cdd84ca0e --- /dev/null +++ b/examples/energy-management-app/energy-management-common/include/ChargingTargetsMemMgr.h @@ -0,0 +1,156 @@ +/* + * + * 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 EnergyEvse { + +/* + * The full Target data structure defined as: + * + * DataModel::List chargingTargetSchedules; + * + * contains a list of ChargingTargetScheduleStructs which in turn contains a list of ChargingTargetStructs. + * This means that somewhere the following memory needs to be allocated in the case where + * the Target is at its maximum size: + * + * ChargingTargetStruct::Type mDailyChargingTargets[kEvseTargetsMaxNumberOfDays][kEvseTargetsMaxTargetsPerDay] + * + * This is 1680B. + * + * However it is likely the number of chargingTargets configured will be considerably less. To avoid + * allocating the maximum possible Target size, each List is allocated + * separately. This class handles that allocation. + * + * When iterating through the chargingTargetSchedules, an index in this list is kept and the + * ChargingTargetsMemMgr::PrepareDaySchedule must be called so this object knows which day schedule it is tracking. + * This will free any previous memory allocated for the day schedule in this object. + * + * There are then three usage cases: + * + * 1. When loading the Target from persistent storage. In this scenario, it is not known upfront + * how many chargingTargets are associated with this day schedule so ChargingTargetsMemMgr::AddChargingTarget() + * needs to be called as each individual chargingTarget is loaded from persistent data. + * + * Once the chargingTargets for the day schedule have been loaded, ChargingTargetsMemMgr::AllocAndCopy() is + * called to allocate the memory to store the chargingTargets and the chargingTargets are copied. + * + * 2. When updating a Target and a day schedule is unaffected, the chargingTargets associated with + * day schedule need copying. The following should be called: + * + * ChargingTargetsMemMgr::AllocAndCopy(const DataModel::List & chargingTargets) + * + * 3. When in SetTargets, a new list of chargingTargets needs to be added to a day schedule, the following + * should be called: + * + * ChargingTargetsMemMgr::AllocAndCopy(const DataModel::DecodableList & + * chargingTargets) + * + * Having allocated and copied the chargingTargets accordingly, they can be added to a + * DataModel::List(ChargingTargetsMemMgr::GetChargingTargets(), + * ChargingTargetsMemMgr::GetNumDailyChargingTargets()); + * + * All memory allocated by this object is released When the ChargingTargetsMemMgr destructor is called. + * + */ + +class ChargingTargetsMemMgr +{ +public: + ChargingTargetsMemMgr(); + ~ChargingTargetsMemMgr(); + + /** + * @brief This method prepares a new day schedule. Subsequent calls to GetChargingTargets + * and the AllocAndCopy methods below will reference this day schedule. + * + * @param chargingTargetSchedulesIdx - The new day schedule index + */ + void PrepareDaySchedule(uint16_t chargingTargetSchedulesIdx); + + /** + * @brief Called as each individual chargingTarget is loaded from persistent data. + * When loading the Target from persistent storage, it is not known upfront + * how many chargingTargets are associated with this day schedule so + * ChargingTargetsMemMgr::AddChargingTarget() needs to be called as each individual + * chargingTarget is loaded from persistent data. + * + * @param chargingTarget - The chargingTarget that will be added into the current day schedule + */ + void AddChargingTarget(const EnergyEvse::Structs::ChargingTargetStruct::Type & chargingTarget); + + /** + * @brief Called to allocate and copy the chargingTargets added via AddChargingTarget into the + * current day schedule as set by PrepareDaySchedule(). + */ + CHIP_ERROR AllocAndCopy(); + + /** + * @brief Called to allocate and copy the chargingTargets into the current day schedule as set + * set by PrepareDaySchedule(). + * If an attempt is made to add more than kEvseTargetsMaxTargetsPerDay chargingTargets + * for the current day schedule, then the chargingTarget is not added and an error message + * is printed. + * + * @param chargingTargets - The chargingTargets to add into the current day schedule + */ + CHIP_ERROR AllocAndCopy(const DataModel::List & chargingTargets); + + /** + * @brief Called to allocate and copy the chargingTargets into the current day schedule as set + * set by PrepareDaySchedule(). + * + * @param chargingTargets - The chargingTargets to add into the current day schedule + */ + CHIP_ERROR AllocAndCopy(const DataModel::DecodableList & chargingTargets); + + /** + * @brief Returns the list of chargingTargets associated with the current day schedule. + * + * @return The charging targets associated with the current day schedule. + */ + EnergyEvse::Structs::ChargingTargetStruct::Type * GetChargingTargets() const; + + /** + * @brief Returns the number of chargingTargets associated with current day schedule. + * + * @return Returns the number of chargingTargets associated with current day schedule. + */ + uint16_t GetNumDailyChargingTargets() const; + +private: + EnergyEvse::Structs::ChargingTargetStruct::Type * mpListOfDays[kEvseTargetsMaxNumberOfDays]; + EnergyEvse::Structs::ChargingTargetStruct::Type mDailyChargingTargets[kEvseTargetsMaxTargetsPerDay]; + uint16_t mChargingTargetSchedulesIdx; + uint16_t mNumDailyChargingTargets; +}; + +} // namespace EnergyEvse +} // namespace Clusters +} // namespace app +} // namespace chip 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 2faf81046cfdf2..b94220d11b28f3 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h @@ -129,6 +129,12 @@ class EVSEManufacturer : public DEMManufacturerDelegate */ static void ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg); + /** + * @brief Simple example to demonstrate how an EVSE can compute the start time + * and duration of a charging schedule + */ + CHIP_ERROR ComputeChargingSchedule(); + /** * @brief Allows a client application to initialise the Accuracy, Measurement types etc */ diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index aed42fa857968a..c43707d9d09589 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -20,6 +20,7 @@ #include "app/clusters/energy-evse-server/energy-evse-server.h" #include +#include #include #include @@ -31,6 +32,10 @@ namespace app { namespace Clusters { namespace EnergyEvse { +// A bitmap of all possible days (the union of the values in +// chip::app::Clusters::EnergyEvse::TargetDayOfWeekBitmap) +constexpr uint8_t kAllTargetDaysMask = 0x7f; + /* Local state machine Events to allow simpler handling of state transitions */ enum EVSEStateMachineEvent { @@ -100,8 +105,11 @@ class EvseSession class EnergyEvseDelegate : public EnergyEvse::Delegate { public: + EnergyEvseDelegate(EvseTargetsDelegate & aDelegate) : EnergyEvse::Delegate() { mEvseTargetsDelegate = &aDelegate; } ~EnergyEvseDelegate(); + EvseTargetsDelegate * GetEvseTargetsDelegate() { return mEvseTargetsDelegate; } + /** * @brief Called when EVSE cluster receives Disable command */ @@ -131,6 +139,37 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate */ Status StartDiagnostics() override; + /** + * @brief Called when EVSE cluster receives the SetTargets command + */ + Status SetTargets( + const DataModel::DecodableList & chargingTargetSchedules) override; + + /** + * @brief Delegate should implement a handler for LoadTargets + * + * This needs to load any stored targets into memory and MUST be called before + * GetTargets is called. + */ + Status LoadTargets() override; + + /** + * @brief Called when EVSE cluster receives the GetTargets command + * + * NOTE: LoadTargets MUST be called GetTargets is called. + */ + Protocols::InteractionModel::Status + GetTargets(DataModel::List & chargingTargetSchedules) override; + + /** + * @brief Called when EVSE cluster receives ClearTargets command + */ + Status ClearTargets() override; + + /* Helper functions for managing targets*/ + Status + ValidateTargets(const DataModel::DecodableList & chargingTargetSchedules); + /** * @brief Called by EVSE Hardware to register a single callback handler */ @@ -153,6 +192,12 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate */ Status ScheduleCheckOnEnabledTimeout(); + /** + * @brief Helper function to get know if the EV is plugged in based on state + * (regardless of if it is actually transferring energy) + */ + bool IsEvsePluggedIn(); + // ----------------------------------------------------------------- // Internal API to allow an EVSE to change its internal state etc Status HwSetMaxHardwareCurrentLimit(int64_t currentmA); @@ -209,9 +254,16 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate /* PREF attributes */ DataModel::Nullable GetNextChargeStartTime() override; + CHIP_ERROR SetNextChargeStartTime(DataModel::Nullable newNextChargeStartTimeUtc); + DataModel::Nullable GetNextChargeTargetTime() override; + CHIP_ERROR SetNextChargeTargetTime(DataModel::Nullable newNextChargeTargetTimeUtc); + DataModel::Nullable GetNextChargeRequiredEnergy() override; + CHIP_ERROR SetNextChargeRequiredEnergy(DataModel::Nullable newNextChargeRequiredEnergyMilliWattH); + DataModel::Nullable GetNextChargeTargetSoC() override; + CHIP_ERROR SetNextChargeTargetSoC(DataModel::Nullable newValue); DataModel::Nullable GetApproximateEVEfficiency() override; CHIP_ERROR SetApproximateEVEfficiency(DataModel::Nullable) override; @@ -229,11 +281,11 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate private: /* Constants */ - static constexpr int kDefaultMinChargeCurrent = 6000; /* 6A */ - static constexpr int kDefaultUserMaximumChargeCurrent = kMaximumChargeCurrent; /* 80A */ - static constexpr int kDefaultRandomizationDelayWindow = 600; /* 600s */ - static constexpr int kMaxVehicleIDBufSize = 32; - static constexpr int kPeriodicCheckIntervalRealTimeClockNotSynced = 30; + static constexpr int kDefaultMinChargeCurrent_mA = 6000; /* 6A */ + static constexpr int kDefaultUserMaximumChargeCurrent_mA = 80000; /* 80A */ + static constexpr int kDefaultRandomizationDelayWindow_sec = 600; /* 600s */ + static constexpr int kMaxVehicleIDBufSize = 32; + static constexpr int kPeriodicCheckIntervalRealTimeClockNotSynced_sec = 30; /* private variables for controlling the hardware - these are not attributes */ int64_t mMaxHardwareCurrentLimit = 0; /* Hardware current limit in mA */ @@ -250,6 +302,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate EVSECallbackWrapper mCallbacks = { .handler = nullptr, .arg = 0 }; /* Wrapper to allow callbacks to be registered */ Status NotifyApplicationCurrentLimitChange(int64_t maximumChargeCurrent); Status NotifyApplicationStateChange(); + Status NotifyApplicationChargingPreferencesChange(); Status GetEVSEEnergyMeterValue(ChargingDischargingType meterType, int64_t & aMeterValue); /* Local State machine handling */ @@ -285,11 +338,11 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate DataModel::Nullable mChargingEnabledUntil; // TODO Default to 0 to indicate disabled DataModel::Nullable mDischargingEnabledUntil; // TODO Default to 0 to indicate disabled int64_t mCircuitCapacity = 0; - int64_t mMinimumChargeCurrent = kDefaultMinChargeCurrent; + int64_t mMinimumChargeCurrent = kDefaultMinChargeCurrent_mA; int64_t mMaximumChargeCurrent = 0; int64_t mMaximumDischargeCurrent = 0; - int64_t mUserMaximumChargeCurrent = kDefaultUserMaximumChargeCurrent; // TODO update spec - uint32_t mRandomizationDelayWindow = kDefaultRandomizationDelayWindow; + int64_t mUserMaximumChargeCurrent = kDefaultUserMaximumChargeCurrent_mA; // TODO update spec + uint32_t mRandomizationDelayWindow = kDefaultRandomizationDelayWindow_sec; /* PREF attributes */ DataModel::Nullable mNextChargeStartTime; DataModel::Nullable mNextChargeTargetTime; @@ -309,6 +362,9 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate /* Helper variable to hold meter val since last EnergyTransferStarted event */ int64_t mMeterValueAtEnergyTransferStart; + + /* Targets Delegate */ + EvseTargetsDelegate * mEvseTargetsDelegate = nullptr; }; } // namespace EnergyEvse diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseTargetsStore.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseTargetsStore.h new file mode 100644 index 00000000000000..97379f8470e0ae --- /dev/null +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseTargetsStore.h @@ -0,0 +1,123 @@ +/* + * + * 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 +#include +#include +#include +#include + +#include + +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace EnergyEvse { + +class EvseTargetsDelegate : public chip::FabricTable::Delegate +{ +public: + EvseTargetsDelegate(); + ~EvseTargetsDelegate(); + + CHIP_ERROR Init(PersistentStorageDelegate * targetStore); + + /** + * @brief Delegate should implement a handler for LoadTargets + * + * This needs to load any stored targets into memory + */ + CHIP_ERROR LoadTargets(); + + /** + * @brief This returns a reference to the existing targets + */ + const DataModel::List & GetTargets(); + + /** + * @brief Copies a ChargingTargetSchedule into our store + * + * @param [in] an entry from the SetTargets list containing: + * dayOfWeekForSequence and chargingTargets (list) + * + * This routine scans the existing targets to see if we have a day of week + * set that matches the new target dayOfWeek bits. If there is an existing + * matching day then it replaces the days existing targets with the new entry + */ + CHIP_ERROR SetTargets( + const DataModel::DecodableList & chargingTargetSchedulesChanges); + + /** + * @brief This deletes all targets and resets the list to empty + */ + CHIP_ERROR ClearTargets(); + + /** + * Part of the FabricTable::Delegate interface. Gets called when a fabric is deleted, such as on FabricTable::Delete(). + **/ + virtual void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override; + +private: + // This is the upper bound in bytes of the TLV storage required to store the chargingTargetSchedulesList + static uint16_t GetTlvSizeUpperBound(); + + CHIP_ERROR SaveTargets(DataModel::List & chargingTargetSchedulesList); + + // For debug purposes + void PrintTargets(const DataModel::List & chargingTargetSchedules); + +protected: + enum class TargetEntryTag : uint8_t + { + kTargetEntry = 1, + kDayOfWeek = 2, + kChargingTargetsList = 3, + kChargingTargetsStruct = 4, + kTargetTime = 5, + kTargetSoC = 6, + kAddedEnergy = 7, + }; + +private: + // Object to handle the allocation of memory for the chargingTargets + ChargingTargetsMemMgr mChargingTargets; + + // Need memory to store the ChargingTargetScheduleStruct as this is pointed to from a + // List + Structs::ChargingTargetScheduleStruct::Type mChargingTargetSchedulesArray[kEvseTargetsMaxNumberOfDays]; + + // The current Target definition + DataModel::List mChargingTargetSchedulesList; + + // Pointer to the PeristentStorage + PersistentStorageDelegate * mpTargetStore = nullptr; + + // Need a key to store the Charging Preference Targets which is a TLV of list of lists + static constexpr const char * spEvseTargetsKeyName = "g/ev/targ"; +}; + +} // namespace EnergyEvse +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp b/examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp new file mode 100644 index 00000000000000..1642b76882178b --- /dev/null +++ b/examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp @@ -0,0 +1,193 @@ +/* + * + * 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 "ChargingTargetsMemMgr.h" + +using namespace chip; +using namespace chip::app; +using namespace chip::app::DataModel; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::EnergyEvse; + +ChargingTargetsMemMgr::ChargingTargetsMemMgr() : mChargingTargetSchedulesIdx(0), mNumDailyChargingTargets(0) +{ + memset(mpListOfDays, 0, sizeof(mpListOfDays)); +} + +ChargingTargetsMemMgr::~ChargingTargetsMemMgr() +{ + // Free all memory allocated for the charging targets + for (uint16_t idx = 0; idx < kEvseTargetsMaxNumberOfDays; idx++) + { + if (mpListOfDays[idx] != nullptr) + { + chip::Platform::Delete(mpListOfDays[idx]); + } + } +} + +void ChargingTargetsMemMgr::PrepareDaySchedule(uint16_t chargingTargetSchedulesIdx) +{ + // MUST be called for each entry in DataModel::List chargingTargetSchedules + mNumDailyChargingTargets = 0; + + // Should not occur but just to be safe + if (chargingTargetSchedulesIdx >= kEvseTargetsMaxNumberOfDays) + { + ChipLogError(AppServer, "PrepareDaySchedule bad chargingTargetSchedulesIdx %u", chargingTargetSchedulesIdx); + return; + } + + mChargingTargetSchedulesIdx = chargingTargetSchedulesIdx; + + // Free up any memory associated with this targetSchedule + if (mpListOfDays[mChargingTargetSchedulesIdx] != nullptr) + { + chip::Platform::MemoryFree(mpListOfDays[mChargingTargetSchedulesIdx]); + mpListOfDays[mChargingTargetSchedulesIdx] = nullptr; + } +} + +void ChargingTargetsMemMgr::AddChargingTarget(const EnergyEvse::Structs::ChargingTargetStruct::Type & chargingTarget) +{ + if (mNumDailyChargingTargets < kEvseTargetsMaxTargetsPerDay) + { + mDailyChargingTargets[mNumDailyChargingTargets++] = chargingTarget; + } + else + { + ChipLogError(AppServer, "AddChargingTarget: trying to add too many chargingTargets"); + } +} + +EnergyEvse::Structs::ChargingTargetStruct::Type * ChargingTargetsMemMgr::GetChargingTargets() const +{ + return mpListOfDays[mChargingTargetSchedulesIdx]; +} + +uint16_t ChargingTargetsMemMgr::GetNumDailyChargingTargets() const +{ + return mNumDailyChargingTargets; +} + +CHIP_ERROR ChargingTargetsMemMgr::AllocAndCopy() +{ + // NOTE: ChargingTargetsMemMgr::PrepareDaySchedule() must be called as specified in the class comments in + // ChargingTargetsMemMgr.h before this method can be called. + + VerifyOrDie(mpListOfDays[mChargingTargetSchedulesIdx] == nullptr); + + if (mNumDailyChargingTargets > 0) + { + // Allocate the memory first and then use placement new to initialise the memory of each element in the array + mpListOfDays[mChargingTargetSchedulesIdx] = static_cast( + chip::Platform::MemoryAlloc(sizeof(EnergyEvse::Structs::ChargingTargetStruct::Type) * mNumDailyChargingTargets)); + + VerifyOrReturnError(mpListOfDays[mChargingTargetSchedulesIdx] != nullptr, CHIP_ERROR_NO_MEMORY); + + for (uint16_t idx = 0; idx < mNumDailyChargingTargets; idx++) + { + // This will cause the ChargingTargetStruct constructor to be called and this element in the array + new (mpListOfDays[mChargingTargetSchedulesIdx] + idx) EnergyEvse::Structs::ChargingTargetStruct::Type(); + + // Now copy the chargingTarget + mpListOfDays[mChargingTargetSchedulesIdx][idx] = mDailyChargingTargets[idx]; + } + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +ChargingTargetsMemMgr::AllocAndCopy(const DataModel::List & chargingTargets) +{ + // NOTE: ChargingTargetsMemMgr::PrepareDaySchedule() must be called as specified in the class comments in + // ChargingTargetsMemMgr.h before this method can be called. + + VerifyOrDie(mpListOfDays[mChargingTargetSchedulesIdx] == nullptr); + + mNumDailyChargingTargets = static_cast(chargingTargets.size()); + + if (mNumDailyChargingTargets > 0) + { + // Allocate the memory first and then use placement new to initialise the memory of each element in the array + mpListOfDays[mChargingTargetSchedulesIdx] = static_cast( + chip::Platform::MemoryAlloc(sizeof(EnergyEvse::Structs::ChargingTargetStruct::Type) * chargingTargets.size())); + + VerifyOrReturnError(mpListOfDays[mChargingTargetSchedulesIdx] != nullptr, CHIP_ERROR_NO_MEMORY); + + uint16_t idx = 0; + for (auto & chargingTarget : chargingTargets) + { + // This will cause the ChargingTargetStruct constructor to be called and this element in the array + new (mpListOfDays[mChargingTargetSchedulesIdx] + idx) EnergyEvse::Structs::ChargingTargetStruct::Type(); + + // Now copy the chargingTarget + mpListOfDays[mChargingTargetSchedulesIdx][idx] = chargingTarget; + + idx++; + } + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +ChargingTargetsMemMgr::AllocAndCopy(const DataModel::DecodableList & chargingTargets) +{ + // NOTE: ChargingTargetsMemMgr::PrepareDaySchedule() must be called as specified in the class comments in + // ChargingTargetsMemMgr.h before this method can be called. + + VerifyOrDie(mpListOfDays[mChargingTargetSchedulesIdx] == nullptr); + + size_t numDailyChargingTargets = 0; + ReturnErrorOnFailure(chargingTargets.ComputeSize(&numDailyChargingTargets)); + + mNumDailyChargingTargets = static_cast(numDailyChargingTargets); + + if (mNumDailyChargingTargets > 0) + { + // Allocate the memory first and then use placement new to initialise the memory of each element in the array + mpListOfDays[mChargingTargetSchedulesIdx] = static_cast( + chip::Platform::MemoryAlloc(sizeof(EnergyEvse::Structs::ChargingTargetStruct::Type) * mNumDailyChargingTargets)); + + VerifyOrReturnError(mpListOfDays[mChargingTargetSchedulesIdx] != nullptr, CHIP_ERROR_NO_MEMORY); + + uint16_t idx = 0; + auto it = chargingTargets.begin(); + while (it.Next()) + { + // Check that the idx is still valid + VerifyOrReturnError(idx < mNumDailyChargingTargets, CHIP_ERROR_INCORRECT_STATE); + + auto & chargingTarget = it.GetValue(); + + // This will cause the ChargingTargetStruct constructor to be called and this element in the array + new (mpListOfDays[mChargingTargetSchedulesIdx] + idx) EnergyEvse::Structs::ChargingTargetStruct::Type(); + + // Now copy the chargingTarget + mpListOfDays[mChargingTargetSchedulesIdx][idx] = chargingTarget; + + idx++; + } + } + + return CHIP_NO_ERROR; +} 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 9006477b3662d5..00ced85a9aa2b6 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -19,7 +19,9 @@ #include #include #include +#include #include +#include #include #include @@ -39,7 +41,6 @@ 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; using namespace chip::app::Clusters::PowerSource; using namespace chip::app::Clusters::PowerSource::Attributes; @@ -115,6 +116,217 @@ CHIP_ERROR EVSEManufacturer::Shutdown() return CHIP_NO_ERROR; } +CHIP_ERROR FindNextTarget(const BitMask dayOfWeekMap, uint16_t minutesPastMidnightNow_m, + uint16_t & targetTimeMinutesPastMidnight_m, DataModel::Nullable & targetSoC, + DataModel::Nullable & addedEnergy_mWh, bool bAllowTargetsInPast) +{ + EnergyEvse::Structs::ChargingTargetScheduleStruct::Type entry; + + uint16_t minTimeToTarget_m = 24 * 60; // 24 hours + bool bFound = false; + + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrReturnError(mn != nullptr, CHIP_ERROR_UNINITIALIZED); + + EnergyEvseDelegate * dg = mn->GetEvseDelegate(); + VerifyOrReturnError(dg != nullptr, CHIP_ERROR_UNINITIALIZED); + + const DataModel::List & chargingTargetSchedules = + dg->GetEvseTargetsDelegate()->GetTargets(); + for (auto & chargingTargetScheduleEntry : chargingTargetSchedules) + { + if (chargingTargetScheduleEntry.dayOfWeekForSequence.HasAny(dayOfWeekMap)) + { + // We've found today's schedule - iterate through the targets on this day + for (auto & chargingTarget : chargingTargetScheduleEntry.chargingTargets) + { + if ((chargingTarget.targetTimeMinutesPastMidnight < minutesPastMidnightNow_m) && (bAllowTargetsInPast == false)) + { + // This target is in the past so move to the next if there is one + continue; + } + + if (chargingTarget.targetTimeMinutesPastMidnight < minTimeToTarget_m) + { + // This is the earliest target found in the day's targets so far + bFound = true; + minTimeToTarget_m = chargingTarget.targetTimeMinutesPastMidnight; + + targetTimeMinutesPastMidnight_m = chargingTarget.targetTimeMinutesPastMidnight; + + if (chargingTarget.targetSoC.HasValue()) + { + targetSoC.SetNonNull(chargingTarget.targetSoC.Value()); + } + else + { + targetSoC.SetNull(); + } + + if (chargingTarget.addedEnergy.HasValue()) + { + addedEnergy_mWh.SetNonNull(chargingTarget.addedEnergy.Value()); + } + else + { + addedEnergy_mWh.SetNull(); + } + } + } + } + + if (bFound) + { + // Skip the rest of the search + break; + } + } + + return bFound ? CHIP_NO_ERROR : CHIP_ERROR_NOT_FOUND; +} + +/** + * @brief Simple example to demonstrate how an EVSE can compute the start time + * and duration of a charging schedule + */ +CHIP_ERROR EVSEManufacturer::ComputeChargingSchedule() +{ + CHIP_ERROR err; + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrReturnError(mn != nullptr, CHIP_ERROR_UNINITIALIZED); + + EnergyEvseDelegate * dg = mn->GetEvseDelegate(); + VerifyOrReturnError(dg != nullptr, CHIP_ERROR_UNINITIALIZED); + + BitMask dayOfWeekMap = 0; + ReturnErrorOnFailure(GetLocalDayOfWeekNow(dayOfWeekMap)); + + uint16_t minutesPastMidnightNow_m = 0; + ReturnErrorOnFailure(GetMinutesPastMidnight(minutesPastMidnightNow_m)); + + uint32_t now_epoch_s = 0; + ReturnErrorOnFailure(GetEpochTS(now_epoch_s)); + + DataModel::Nullable startTime_epoch_s; + DataModel::Nullable targetTime_epoch_s; + DataModel::Nullable targetSoC; + DataModel::Nullable addedEnergy_mWh; + + uint32_t power_W; + uint32_t chargingDuration_s; + uint32_t tempTargetTime_epoch_s; + uint32_t tempStartTime_epoch_s; + uint16_t targetTimeMinutesPastMidnight_m; + + // Initialise the values to Null - if the FindNextTarget finds one, then it will update the value + targetTime_epoch_s.SetNull(); + targetSoC.SetNull(); + addedEnergy_mWh.SetNull(); + startTime_epoch_s.SetNull(); // If we FindNextTarget this will be computed below and set to a non null value + + /* We can only compute charging schedules if the EV is plugged in and the charging is enabled + * so we know the charging current - i.e. can get the max power, and therefore can calculate + * the charging duration and hence start time + */ + if (dg->IsEvsePluggedIn() && dg->GetSupplyState() == SupplyStateEnum::kChargingEnabled) + { + uint8_t searchDay = 0; + while (searchDay < 2) + { + err = FindNextTarget(dayOfWeekMap, minutesPastMidnightNow_m, targetTimeMinutesPastMidnight_m, targetSoC, + addedEnergy_mWh, (searchDay != 0)); + if (err == CHIP_ERROR_NOT_FOUND) + { + // We didn't find one for today, try tomorrow + searchDay++; + dayOfWeekMap = BitMask((dayOfWeekMap.Raw() << 1) & kAllTargetDaysMask); + + if (!dayOfWeekMap.HasAny()) + { + // Must be Saturday and shifted off, so set it to Sunday + dayOfWeekMap = BitMask(TargetDayOfWeekBitmap::kSunday); + } + } + else + { + break; // We found a target or we error'd out for some other reason + } + } + + if (err == CHIP_NO_ERROR) + { + /* Set the target Time in epoch_s format*/ + tempTargetTime_epoch_s = + ((now_epoch_s / 60) + targetTimeMinutesPastMidnight_m + (searchDay * 1440) - minutesPastMidnightNow_m) * 60; + targetTime_epoch_s.SetNonNull(tempTargetTime_epoch_s); + + if (!targetSoC.IsNull()) + { + if (targetSoC.Value() != 100) + { + ChipLogError(AppServer, "EVSE WARNING: TargetSoC is not 100%% and we don't know the EV SoC!"); + } + // We don't know the Vehicle SoC so we must charge now + // TODO make this use the SoC featureMap to determine if this is an error + startTime_epoch_s.SetNonNull(now_epoch_s); + } + else + { + // We expect to use AddedEnergy to determine the charging start time + if (addedEnergy_mWh.IsNull()) + { + ChipLogError(AppServer, "EVSE ERROR: Neither TargetSoC or AddedEnergy has been provided"); + return CHIP_ERROR_INTERNAL; + } + // Simple optimizer - assume a flat tariff throughout the day + // Compute power from nominal voltage and maxChargingRate + // GetMaximumChargeCurrent returns mA, but to help avoid overflow + // We use V (not mV) and compute power to the nearest Watt + power_W = static_cast((230 * dg->GetMaximumChargeCurrent()) / + 1000); // TODO don't use 230V - not all markets will use that + if (power_W == 0) + { + ChipLogError(AppServer, "EVSE Error: MaxCurrent = 0Amp - Can't schedule charging"); + return CHIP_ERROR_INTERNAL; + } + + // Time to charge(seconds) = (3600 * Energy(mWh) / Power(W)) / 1000 + // to avoid using floats we multiply by 36 and then divide by 10 (instead of x3600 and dividing by 1000) + chargingDuration_s = static_cast(((addedEnergy_mWh.Value() / power_W) * 36) / 10); + + // Add in 15 minutes leeway to account for slow starting vehicles + // that need to condition the battery or if it is cold etc + chargingDuration_s += (15 * 60); + + // A price optimizer can look for cheapest time of day + // However for now we'll start charging as late as possible + tempStartTime_epoch_s = tempTargetTime_epoch_s - chargingDuration_s; + + if (tempStartTime_epoch_s < now_epoch_s) + { + // we need to turn on the EVSE now - it won't have enough time to reach the target + startTime_epoch_s.SetNonNull(now_epoch_s); + // TODO call function to turn on the EV + } + else + { + // we turn off the EVSE for now + startTime_epoch_s.SetNonNull(tempStartTime_epoch_s); + // TODO have a periodic timer which checks if we should turn on the charger now + } + } + } + } + + // Update the attributes to allow a UI to inform the user + dg->SetNextChargeStartTime(startTime_epoch_s); + dg->SetNextChargeTargetTime(targetTime_epoch_s); + dg->SetNextChargeRequiredEnergy(addedEnergy_mWh); + dg->SetNextChargeTargetSoC(targetSoC); + + return err; +} + /** * @brief Allows a client application to initialise the Accuracy, Measurement types etc */ @@ -187,6 +399,8 @@ CHIP_ERROR EVSEManufacturer::SendPowerReading(EndpointId aEndpointId, int64_t aA return CHIP_NO_ERROR; } +using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Structs; + /** * @brief Allows a client application to send cumulative energy readings into the system * @@ -349,10 +563,12 @@ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_ { case EVSECallbackType::StateChanged: ChipLogProgress(AppServer, "EVSE callback - state changed"); + pClass->ComputeChargingSchedule(); break; case EVSECallbackType::ChargeCurrentChanged: ChipLogProgress(AppServer, "EVSE callback - maxChargeCurrent changed to %ld", static_cast(cb->ChargingCurrent.maximumChargeCurrent)); + pClass->ComputeChargingSchedule(); break; case EVSECallbackType::EnergyMeterReadingRequested: ChipLogProgress(AppServer, "EVSE callback - EnergyMeterReadingRequested"); @@ -366,6 +582,11 @@ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_ } break; + case EVSECallbackType::ChargingPreferencesChanged: + ChipLogProgress(AppServer, "EVSE callback - ChargingPreferencesChanged"); + pClass->ComputeChargingSchedule(); + break; + default: ChipLogError(AppServer, "Unhandled EVSE Callback type %d", static_cast(cb->type)); } diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp index b0cfd6fc5ce0ef..6266be245c2a4a 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -17,6 +17,7 @@ */ #include +#include #include #include #include @@ -42,13 +43,6 @@ EnergyEvseDelegate::~EnergyEvseDelegate() } } -/** - * @brief Helper function to get current timestamp in Epoch format - * - * @param chipEpoch reference to hold return timestamp - */ -CHIP_ERROR GetEpochTS(uint32_t & chipEpoch); - /** * @brief Called when EVSE cluster receives Disable command */ @@ -63,7 +57,9 @@ Status EnergyEvseDelegate::Disable() /* update MinimumChargeCurrent & MaximumChargeCurrent to 0 */ SetMinimumChargeCurrent(0); - SetMaximumChargeCurrent(0); + + mMaximumChargingCurrentLimitFromCommand = 0; + ComputeMaxChargeCurrentLimit(); /* update MaximumDischargeCurrent to 0 */ SetMaximumDischargeCurrent(0); @@ -83,13 +79,13 @@ Status EnergyEvseDelegate::EnableCharging(const DataModel::Nullable & { ChipLogProgress(AppServer, "EnergyEvseDelegate::EnableCharging()"); - if (maximumChargeCurrent < kMinimumChargeCurrent || maximumChargeCurrent > kMaximumChargeCurrent) + if (maximumChargeCurrent < kMinimumChargeCurrent) { ChipLogError(AppServer, "Maximum Current outside limits"); return Status::ConstraintError; } - if (minimumChargeCurrent < kMinimumChargeCurrent || minimumChargeCurrent > kMaximumChargeCurrent) + if (minimumChargeCurrent < kMinimumChargeCurrent) { ChipLogError(AppServer, "Maximum Current outside limits"); return Status::ConstraintError; @@ -177,7 +173,7 @@ Status EnergyEvseDelegate::ScheduleCheckOnEnabledTimeout() return Status::Success; } - CHIP_ERROR err = GetEpochTS(chipEpoch); + CHIP_ERROR err = DeviceEnergyManagement::GetEpochTS(chipEpoch); if (err == CHIP_NO_ERROR) { /* time is sync'd */ @@ -198,7 +194,7 @@ Status EnergyEvseDelegate::ScheduleCheckOnEnabledTimeout() else if (err == CHIP_ERROR_REAL_TIME_NOT_SYNCED) { /* Real time isn't sync'd -lets check again in 30 seconds - otherwise keep the charger enabled */ - DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(kPeriodicCheckIntervalRealTimeClockNotSynced), + DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(kPeriodicCheckIntervalRealTimeClockNotSynced_sec), EvseCheckTimerExpiry, this); } return Status::Success; @@ -234,6 +230,83 @@ Status EnergyEvseDelegate::StartDiagnostics() return Status::Success; } +/** + * @brief Called when EVSE cluster receives SetTargets command + */ +Status EnergyEvseDelegate::SetTargets( + const DataModel::DecodableList & chargingTargetSchedules) +{ + ChipLogProgress(AppServer, "EnergyEvseDelegate::SetTargets()"); + + EvseTargetsDelegate * targets = GetEvseTargetsDelegate(); + VerifyOrReturnError(targets != nullptr, Status::Failure); + + CHIP_ERROR err = targets->SetTargets(chargingTargetSchedules); + VerifyOrReturnError(err == CHIP_NO_ERROR, StatusIB(err).mStatus); + + /* The Application needs to be told that the Targets have been updated + * so it can potentially re-optimize the charging start time etc + */ + NotifyApplicationChargingPreferencesChange(); + + return Status::Success; +} + +Status EnergyEvseDelegate::LoadTargets() +{ + ChipLogProgress(AppServer, "EnergyEvseDelegate::LoadTargets()"); + + EvseTargetsDelegate * targets = GetEvseTargetsDelegate(); + VerifyOrReturnError(targets != nullptr, StatusIB(CHIP_ERROR_UNINITIALIZED).mStatus); + + CHIP_ERROR err = targets->LoadTargets(); + VerifyOrReturnError(err == CHIP_NO_ERROR, StatusIB(err).mStatus); + + return Status::Success; +} + +Status EnergyEvseDelegate::GetTargets(DataModel::List & chargingTargetSchedules) +{ + ChipLogProgress(AppServer, "EnergyEvseDelegate::GetTargets()"); + + EvseTargetsDelegate * targets = GetEvseTargetsDelegate(); + VerifyOrReturnError(targets != nullptr, StatusIB(CHIP_ERROR_UNINITIALIZED).mStatus); + + chargingTargetSchedules = targets->GetTargets(); + + return Status::Success; +} + +/** + * @brief Called when EVSE cluster receives ClearTargets command + */ +Status EnergyEvseDelegate::ClearTargets() +{ + ChipLogProgress(AppServer, "EnergyEvseDelegate::ClearTargets()"); + + CHIP_ERROR err; + + EvseTargetsDelegate * targets = GetEvseTargetsDelegate(); + if (targets == nullptr) + { + return StatusIB(CHIP_ERROR_UNINITIALIZED).mStatus; + } + + err = targets->ClearTargets(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Failed to clear Evse targets: %" CHIP_ERROR_FORMAT, err.Format()); + return Status::Failure; + } + + /* The Application needs to be told that the Targets have been deleted + * so it can potentially re-optimize the charging start time etc + */ + NotifyApplicationChargingPreferencesChange(); + + return Status::Success; +} + /* --------------------------------------------------------------------------- * EVSE Hardware interface below */ @@ -269,7 +342,7 @@ Status EnergyEvseDelegate::HwRegisterEvseCallbackHandler(EVSECallbackFunc handle */ Status EnergyEvseDelegate::HwSetMaxHardwareCurrentLimit(int64_t currentmA) { - if (currentmA < kMinimumChargeCurrent || currentmA > kMaximumChargeCurrent) + if (currentmA < kMinimumChargeCurrent) { return Status::ConstraintError; } @@ -291,7 +364,7 @@ Status EnergyEvseDelegate::HwSetMaxHardwareCurrentLimit(int64_t currentmA) */ Status EnergyEvseDelegate::HwSetCircuitCapacity(int64_t currentmA) { - if (currentmA < kMinimumChargeCurrent || currentmA > kMaximumChargeCurrent) + if (currentmA < kMinimumChargeCurrent) { return Status::ConstraintError; } @@ -316,7 +389,7 @@ Status EnergyEvseDelegate::HwSetCircuitCapacity(int64_t currentmA) */ Status EnergyEvseDelegate::HwSetCableAssemblyLimit(int64_t currentmA) { - if (currentmA < kMinimumChargeCurrent || currentmA > kMaximumChargeCurrent) + if (currentmA < kMinimumChargeCurrent) { return Status::ConstraintError; } @@ -909,6 +982,20 @@ Status EnergyEvseDelegate::NotifyApplicationStateChange() return Status::Success; } +Status EnergyEvseDelegate::NotifyApplicationChargingPreferencesChange() +{ + EVSECbInfo cbInfo; + + cbInfo.type = EVSECallbackType::ChargingPreferencesChanged; + + if (mCallbacks.handler != nullptr) + { + mCallbacks.handler(&cbInfo, mCallbacks.arg); + } + + return Status::Success; +} + Status EnergyEvseDelegate::GetEVSEEnergyMeterValue(ChargingDischargingType meterType, int64_t & aMeterValue) { EVSECbInfo cbInfo; @@ -1227,7 +1314,7 @@ CHIP_ERROR EnergyEvseDelegate::SetCircuitCapacity(int64_t newValue) { int64_t oldValue = mCircuitCapacity; - if (newValue >= kMaximumChargeCurrent) + if (newValue < 0) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } @@ -1251,7 +1338,7 @@ CHIP_ERROR EnergyEvseDelegate::SetMinimumChargeCurrent(int64_t newValue) { int64_t oldValue = mMinimumChargeCurrent; - if (newValue >= kMaximumChargeCurrent) + if (newValue < 0) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } @@ -1271,24 +1358,6 @@ int64_t EnergyEvseDelegate::GetMaximumChargeCurrent() return mMaximumChargeCurrent; } -CHIP_ERROR EnergyEvseDelegate::SetMaximumChargeCurrent(int64_t newValue) -{ - int64_t oldValue = mMaximumChargeCurrent; - - if (newValue >= kMaximumChargeCurrent) - { - return CHIP_IM_GLOBAL_STATUS(ConstraintError); - } - - mMaximumChargeCurrent = newValue; - if (oldValue != mMaximumChargeCurrent) - { - ChipLogDetail(AppServer, "MaximumChargeCurrent updated to %ld", static_cast(mMaximumChargeCurrent)); - MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, MaximumChargeCurrent::Id); - } - return CHIP_NO_ERROR; -} - /* MaximumDischargeCurrent */ int64_t EnergyEvseDelegate::GetMaximumDischargeCurrent() { @@ -1299,7 +1368,7 @@ CHIP_ERROR EnergyEvseDelegate::SetMaximumDischargeCurrent(int64_t newValue) { int64_t oldValue = mMaximumDischargeCurrent; - if (newValue >= kMaximumChargeCurrent) + if (newValue < 0) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } @@ -1321,7 +1390,7 @@ int64_t EnergyEvseDelegate::GetUserMaximumChargeCurrent() CHIP_ERROR EnergyEvseDelegate::SetUserMaximumChargeCurrent(int64_t newValue) { - if ((newValue < 0) || (newValue > kMaximumChargeCurrent)) + if (newValue < 0) { return CHIP_IM_GLOBAL_STATUS(ConstraintError); } @@ -1378,18 +1447,105 @@ DataModel::Nullable EnergyEvseDelegate::GetNextChargeStartTime() { return mNextChargeStartTime; } +CHIP_ERROR EnergyEvseDelegate::SetNextChargeStartTime(DataModel::Nullable newNextChargeStartTimeUtc) +{ + if (newNextChargeStartTimeUtc == mNextChargeStartTime) + { + return CHIP_NO_ERROR; + } + + mNextChargeStartTime = newNextChargeStartTimeUtc; + if (mNextChargeStartTime.IsNull()) + { + ChipLogDetail(AppServer, "NextChargeStartTime updated to Null"); + } + else + { + ChipLogDetail(AppServer, "NextChargeStartTime updated to %lu", + static_cast(mNextChargeStartTime.Value())); + } + + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, NextChargeStartTime::Id); + + return CHIP_NO_ERROR; +} + DataModel::Nullable EnergyEvseDelegate::GetNextChargeTargetTime() { return mNextChargeTargetTime; } +CHIP_ERROR EnergyEvseDelegate::SetNextChargeTargetTime(DataModel::Nullable newNextChargeTargetTimeUtc) +{ + if (newNextChargeTargetTimeUtc == mNextChargeTargetTime) + { + return CHIP_NO_ERROR; + } + + mNextChargeTargetTime = newNextChargeTargetTimeUtc; + if (mNextChargeTargetTime.IsNull()) + { + ChipLogDetail(AppServer, "NextChargeTargetTime updated to Null"); + } + else + { + ChipLogDetail(AppServer, "NextChargeTargetTime updated to %lu", + static_cast(mNextChargeTargetTime.Value())); + } + + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, NextChargeTargetTime::Id); + + return CHIP_NO_ERROR; +} + DataModel::Nullable EnergyEvseDelegate::GetNextChargeRequiredEnergy() { return mNextChargeRequiredEnergy; } +CHIP_ERROR EnergyEvseDelegate::SetNextChargeRequiredEnergy(DataModel::Nullable newNextChargeRequiredEnergyMilliWattH) +{ + if (mNextChargeRequiredEnergy == newNextChargeRequiredEnergyMilliWattH) + { + return CHIP_NO_ERROR; + } + + mNextChargeRequiredEnergy = newNextChargeRequiredEnergyMilliWattH; + if (mNextChargeRequiredEnergy.IsNull()) + { + ChipLogDetail(AppServer, "NextChargeRequiredEnergy updated to Null"); + } + else + { + ChipLogDetail(AppServer, "NextChargeRequiredEnergy updated to %ld", static_cast(mNextChargeRequiredEnergy.Value())); + } + + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, NextChargeRequiredEnergy::Id); + + return CHIP_NO_ERROR; +} + DataModel::Nullable EnergyEvseDelegate::GetNextChargeTargetSoC() { return mNextChargeTargetSoC; } +CHIP_ERROR EnergyEvseDelegate::SetNextChargeTargetSoC(DataModel::Nullable newValue) +{ + DataModel::Nullable oldValue = mNextChargeTargetSoC; + + mNextChargeTargetSoC = newValue; + if (oldValue != newValue) + { + if (newValue.IsNull()) + { + ChipLogDetail(AppServer, "NextChargeTargetSoC updated to Null"); + } + else + { + ChipLogDetail(AppServer, "NextChargeTargetSoC updated to %d %%", mNextChargeTargetSoC.Value()); + } + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, NextChargeTargetSoC::Id); + } + return CHIP_NO_ERROR; +} /* ApproximateEVEfficiency */ DataModel::Nullable EnergyEvseDelegate::GetApproximateEVEfficiency() @@ -1402,7 +1558,7 @@ CHIP_ERROR EnergyEvseDelegate::SetApproximateEVEfficiency(DataModel::Nullable oldValue = mApproximateEVEfficiency; mApproximateEVEfficiency = newValue; - if ((oldValue != newValue)) + if (oldValue != newValue) { if (newValue.IsNull()) { @@ -1458,42 +1614,13 @@ DataModel::Nullable EnergyEvseDelegate::GetSessionEnergyDischarged() } /** - * @brief Helper function to get current timestamp in Epoch format - * - * @param chipEpoch reference to hold return timestamp + * @brief Helper function to get know if the EV is plugged in based on state + * (regardless of if it is actually transferring energy) */ -CHIP_ERROR GetEpochTS(uint32_t & chipEpoch) +bool EnergyEvseDelegate::IsEvsePluggedIn() { - chipEpoch = 0; - - System::Clock::Milliseconds64 cTMs; - CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(cTMs); - - /* If the GetClock_RealTimeMS returns CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE, then - * This platform cannot ever report real time ! - * This should not be certifiable since getting time is a Mandatory - * feature of EVSE Cluster - */ - if (err == CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE) - { - ChipLogError(Zcl, "Platform does not support GetClock_RealTimeMS. Check EVSE certification requirements!"); - return err; - } - - if (err != CHIP_NO_ERROR) - { - ChipLogError(Zcl, "EVSE: Unable to get current time - err:%" CHIP_ERROR_FORMAT, err.Format()); - return err; - } - - auto unixEpoch = std::chrono::duration_cast(cTMs).count(); - if (!UnixEpochToChipEpochTime(unixEpoch, chipEpoch)) - { - ChipLogError(Zcl, "EVSE: unable to convert Unix Epoch time to Matter Epoch Time"); - return err; - } - - return CHIP_NO_ERROR; + return (mState == StateEnum::kPluggedInCharging || mState == StateEnum::kPluggedInDemand || + mState == StateEnum::kPluggedInDischarging || mState == StateEnum::kPluggedInNoDemand); } /** @@ -1506,7 +1633,7 @@ void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMe { /* Get Timestamp */ uint32_t chipEpoch = 0; - CHIP_ERROR err = GetEpochTS(chipEpoch); + CHIP_ERROR err = DeviceEnergyManagement::GetEpochTS(chipEpoch); if (err != CHIP_NO_ERROR) { /* Note that the error will be also be logged inside GetErrorTS() - @@ -1548,6 +1675,8 @@ void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMe // TODO persist mSessionEnergyDischargedAtStart } +/*---------------------- EvseSession functions --------------------------*/ + /** * @brief This function updates the session attrs to allow read attributes to return latest values */ @@ -1555,7 +1684,7 @@ void EvseSession::RecalculateSessionDuration() { /* Get Timestamp */ uint32_t chipEpoch = 0; - CHIP_ERROR err = GetEpochTS(chipEpoch); + CHIP_ERROR err = DeviceEnergyManagement::GetEpochTS(chipEpoch); if (err != CHIP_NO_ERROR) { /* Note that the error will be also be logged inside GetErrorTS() - diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseEventTriggers.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseEventTriggers.cpp index 62329068610f47..a164bb0bafb341 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseEventTriggers.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseEventTriggers.cpp @@ -101,6 +101,14 @@ void SetTestEventTrigger_EVChargeDemandClear() dg->HwSetState(sEVSETestEventSaveData.mOldHwStatePluggedInDemand); } +void SetTestEventTrigger_EVTimeOfUseMode() +{ + // TODO - See #34249 +} +void SetTestEventTrigger_EVTimeOfUseModeClear() +{ + // TODO - See #34249 +} void SetTestEventTrigger_EVSEGroundFault() { EnergyEvseDelegate * dg = GetEvseDelegate(); @@ -159,6 +167,10 @@ bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV Charge NoDemand"); SetTestEventTrigger_EVChargeDemandClear(); break; + case EnergyEvseTrigger::kEVTimeOfUseMode: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV TimeOfUse Mode"); + SetTestEventTrigger_EVTimeOfUseMode(); + break; case EnergyEvseTrigger::kEVSEGroundFault: ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE has a GroundFault fault"); SetTestEventTrigger_EVSEGroundFault(); @@ -175,7 +187,10 @@ bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE Diagnostics Completed"); SetTestEventTrigger_EVSEDiagnosticsComplete(); break; - + case EnergyEvseTrigger::kEVTimeOfUseModeClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV TimeOfUse Mode clear"); + SetTestEventTrigger_EVTimeOfUseModeClear(); + break; default: return false; } diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseMain.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseMain.cpp index 0a4bc0456e2254..bd22c67eafd9b5 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseMain.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseMain.cpp @@ -49,6 +49,7 @@ using namespace chip::app::Clusters::ElectricalEnergyMeasurement; using namespace chip::app::Clusters::PowerTopology; static std::unique_ptr gEvseDelegate; +static std::unique_ptr gEvseTargetsDelegate; static std::unique_ptr gEvseInstance; static std::unique_ptr gDEMDelegate; static std::unique_ptr gDEMInstance; @@ -143,16 +144,24 @@ CHIP_ERROR EnergyEvseInit() { CHIP_ERROR err; - if (gEvseDelegate || gEvseInstance) + if (gEvseDelegate || gEvseInstance || gEvseTargetsDelegate) { - ChipLogError(AppServer, "EVSE Instance or Delegate already exist."); + ChipLogError(AppServer, "EVSE Instance, Delegate or TargetsDelegate already exist."); return CHIP_ERROR_INCORRECT_STATE; } - gEvseDelegate = std::make_unique(); + gEvseTargetsDelegate = std::make_unique(); + if (!gEvseTargetsDelegate) + { + ChipLogError(AppServer, "Failed to allocate memory for EvseTargetsDelegate"); + return CHIP_ERROR_NO_MEMORY; + } + + gEvseDelegate = std::make_unique(*gEvseTargetsDelegate); if (!gEvseDelegate) { ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseDelegate"); + gEvseTargetsDelegate.reset(); return CHIP_ERROR_NO_MEMORY; } @@ -168,6 +177,7 @@ CHIP_ERROR EnergyEvseInit() if (!gEvseInstance) { ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseManager"); + gEvseTargetsDelegate.reset(); gEvseDelegate.reset(); return CHIP_ERROR_NO_MEMORY; } @@ -176,6 +186,17 @@ CHIP_ERROR EnergyEvseInit() if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "Init failed on gEvseInstance"); + gEvseTargetsDelegate.reset(); + gEvseInstance.reset(); + gEvseDelegate.reset(); + return err; + } + + err = gEvseTargetsDelegate->LoadTargets(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Failed to LoadTargets"); + gEvseTargetsDelegate.reset(); gEvseInstance.reset(); gEvseDelegate.reset(); return err; 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 c8b43342f8bd1c..52a8fec55673dd 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp @@ -18,6 +18,7 @@ #include #include +#include using namespace chip::app; using namespace chip::app::Clusters; @@ -117,6 +118,16 @@ CHIP_ERROR EnergyEvseManager::LoadPersistentAttributes() CHIP_ERROR EnergyEvseManager::Init() { ReturnErrorOnFailure(Instance::Init()); + + // Set up the EnergyEvseTargetsStore and persistent storage delegate + EnergyEvseDelegate * dg = GetDelegate(); + VerifyOrReturnLogError(dg != nullptr, CHIP_ERROR_UNINITIALIZED); + + EvseTargetsDelegate * targetsStore = dg->GetEvseTargetsDelegate(); + VerifyOrReturnLogError(targetsStore != nullptr, CHIP_ERROR_UNINITIALIZED); + + ReturnErrorOnFailure(targetsStore->Init(&Server::GetInstance().GetPersistentStorage())); + return LoadPersistentAttributes(); } diff --git a/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp b/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp new file mode 100644 index 00000000000000..f15e0b7a109817 --- /dev/null +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp @@ -0,0 +1,489 @@ +/* + * + * 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 + +using namespace chip; +using namespace chip::app; +using namespace chip::app::DataModel; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::EnergyEvse; +using chip::Protocols::InteractionModel::Status; + +EvseTargetsDelegate::EvseTargetsDelegate() {} + +EvseTargetsDelegate::~EvseTargetsDelegate() {} + +CHIP_ERROR EvseTargetsDelegate::Init(PersistentStorageDelegate * targetStore) +{ + ChipLogProgress(AppServer, "EVSE: Initializing EvseTargetsDelegate"); + VerifyOrReturnError(targetStore != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + mpTargetStore = targetStore; + + // Set FabricDelegate + chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(this); + + return CHIP_NO_ERROR; +} + +const DataModel::List & EvseTargetsDelegate::GetTargets() +{ + return mChargingTargetSchedulesList; +} + +/* static */ +uint16_t EvseTargetsDelegate::GetTlvSizeUpperBound() +{ + size_t kListOverhead = 4; + size_t chargingTargetStuctEstimate = + TLV::EstimateStructOverhead(sizeof(uint16_t), sizeof(Optional), sizeof(Optional)); + size_t chargingTargetScheduleStructEstimate = TLV::EstimateStructOverhead(sizeof(chip::BitMask)) + + kListOverhead + kEvseTargetsMaxTargetsPerDay * chargingTargetStuctEstimate; + size_t totalEstimate = kEvseTargetsMaxNumberOfDays * chargingTargetScheduleStructEstimate + kListOverhead; + + return static_cast(totalEstimate); +} + +CHIP_ERROR EvseTargetsDelegate::LoadTargets() +{ + // The DataModel::List data structure contains a list of + // ChargingTargetScheduleStructs which in turn contains a list of ChargingTargetStructs. Lists contain pointers + // to objects allocated outside of the List. For mChargingTargetSchedulesList, that memory is allocated in + // mChargingTargets and mChargingTargetSchedulesArray. + + Platform::ScopedMemoryBuffer backingBuffer; + uint16_t length = GetTlvSizeUpperBound(); + ReturnErrorCodeIf(!backingBuffer.Calloc(length), CHIP_ERROR_NO_MEMORY); + + CHIP_ERROR err = mpTargetStore->SyncGetKeyValue(spEvseTargetsKeyName, backingBuffer.Get(), length); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + // Targets does not exist persistent storage -> initialise mChargingTargetSchedulesList as empty + mChargingTargetSchedulesList = DataModel::List(); + + return CHIP_NO_ERROR; + } + ReturnErrorOnFailure(err); + + TLV::ScopedBufferTLVReader reader(std::move(backingBuffer), length); + + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag())); + TLV::TLVType arrayType; + ReturnErrorOnFailure(reader.EnterContainer(arrayType)); + + uint16_t chargingTargetSchedulesIdx = 0; + while ((err = reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())) == CHIP_NO_ERROR) + { + TLV::TLVType evseTargetEntryType; + + ReturnErrorOnFailure(reader.EnterContainer(evseTargetEntryType)); + + // Check we are not exceeding the size of the mChargingTargetSchedulesArray + VerifyOrReturnError(chargingTargetSchedulesIdx < kEvseTargetsMaxNumberOfDays, CHIP_ERROR_INCORRECT_STATE); + + // DayOfWeek bitmap + ReturnErrorOnFailure(reader.Next(TLV::ContextTag(TargetEntryTag::kDayOfWeek))); + ReturnErrorOnFailure(reader.Get(mChargingTargetSchedulesArray[chargingTargetSchedulesIdx].dayOfWeekForSequence)); + + ChipLogProgress(AppServer, "LoadTargets: DayOfWeekForSequence = 0x%02x", + mChargingTargetSchedulesArray[chargingTargetSchedulesIdx].dayOfWeekForSequence.GetField( + static_cast(kAllTargetDaysMask))); + + // ChargingTargets List + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_List, TLV::ContextTag(TargetEntryTag::kChargingTargetsList))); + TLV::TLVType chargingTargetsListType; + ReturnErrorOnFailure(reader.EnterContainer(chargingTargetsListType)); + + // The mChargingTargets object handles the allocation of the chargingTargets. Let it know the currentSchedule index + mChargingTargets.PrepareDaySchedule(chargingTargetSchedulesIdx); + + // Load the chargingTargets associated with this schedule + while ((err = reader.Next(TLV::kTLVType_Structure, TLV::ContextTag(TargetEntryTag::kChargingTargetsStruct))) == + CHIP_NO_ERROR) + { + TLV::TLVType chargingTargetsStructType = TLV::kTLVType_Structure; + ReturnErrorOnFailure(reader.EnterContainer(chargingTargetsStructType)); + + // Keep track of the current chargingTarget being loaded + EnergyEvse::Structs::ChargingTargetStruct::Type chargingTarget; + + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + auto type = reader.GetType(); + auto tag = reader.GetTag(); + if (type == TLV::kTLVType_NotSpecified) + { + // Something wrong - we've lost alignment + return CHIP_ERROR_UNEXPECTED_TLV_ELEMENT; + } + + if (tag == TLV::ContextTag(TargetEntryTag::kTargetTime)) + { + ReturnErrorOnFailure(reader.Get(chargingTarget.targetTimeMinutesPastMidnight)); + } + else if (tag == TLV::ContextTag(TargetEntryTag::kTargetSoC)) + { + chip::Percent tempSoC; + ReturnErrorOnFailure(reader.Get(tempSoC)); + chargingTarget.targetSoC.SetValue(tempSoC); + } + else if (tag == TLV::ContextTag(TargetEntryTag::kAddedEnergy)) + { + int64_t tempAddedEnergy; + ReturnErrorOnFailure(reader.Get(tempAddedEnergy)); + chargingTarget.addedEnergy.SetValue(tempAddedEnergy); + } + else + { + // Something else unexpected here + return CHIP_ERROR_UNEXPECTED_TLV_ELEMENT; + } + } + + ReturnErrorOnFailure(reader.ExitContainer(chargingTargetsStructType)); + + ChipLogProgress(AppServer, + "LoadingTargets: targetTimeMinutesPastMidnight %u targetSoC %u addedEnergy 0x" ChipLogFormatX64, + chargingTarget.targetTimeMinutesPastMidnight, chargingTarget.targetSoC.ValueOr(0), + ChipLogValueX64(chargingTarget.addedEnergy.ValueOr(0))); + + // Update mChargingTargets which is tracking the chargingTargets + mChargingTargets.AddChargingTarget(chargingTarget); + } + + ReturnErrorOnFailure(reader.ExitContainer(chargingTargetsListType)); + ReturnErrorOnFailure(reader.ExitContainer(evseTargetEntryType)); + + // Allocate an array for the chargingTargets loaded for this schedule and copy the chargingTargets into that array. + // The allocated array will be pointed to in the List below. + err = mChargingTargets.AllocAndCopy(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "SetTargets: Failed to allocate memory during LoadTargets %s", chip::ErrorStr(err)); + return err; + } + + // Construct the List. mChargingTargetSchedulesArray will be pointed to in the + // List mChargingTargetSchedulesList below + mChargingTargetSchedulesArray[chargingTargetSchedulesIdx].chargingTargets = + chip::app::DataModel::List( + mChargingTargets.GetChargingTargets(), mChargingTargets.GetNumDailyChargingTargets()); + + chargingTargetSchedulesIdx++; + } + + ReturnErrorOnFailure(reader.ExitContainer(arrayType)); + + // Finalise mChargingTargetSchedulesList + mChargingTargetSchedulesList = DataModel::List(mChargingTargetSchedulesArray, + chargingTargetSchedulesIdx); + + return reader.VerifyEndOfContainer(); +} + +/** + * This function tries to compress a list of entries which has: + * dayOfWeek bitmask + * chargingTargetsList + * + * It takes a new entry and scans the existing list to see if the + * dayOfWeek bitmask is already included somewhere + * + * compute bitmask values: + * + * bitmaskA: (entry.bitmask & bitmask) + * work out which bits in the existing entry are the same (overlapping) + * + * Create and append a new entry for the bits that are the same + * newEntry.bitmask = bitmaskA; + * newEntry.chargingTargetsList = chargingTargetsList + * + * if entry.bitmask == bitmaskA + * this entry is being deleted and can share the newEntry + * delete it + * + * bitmaskB = (entry.bitmask & ~bitmask); + * work out which bits in the existing entry are different + * Remove these bits from the existing entry, (effectively deleting them) + * entry.bitmask = bitmaskB; + * + * NOTE: if `all` bits are removed then the existing entry can be deleted, + * but that's not possible because we check for a full match first + * + * We continue walking our list to see if other entries have overlapping bits + * If they do, then the newEntry.bitmask |= bitmaskA + * + */ +CHIP_ERROR EvseTargetsDelegate::SetTargets( + const DataModel::DecodableList & newChargingTargetSchedules) +{ + ChipLogProgress(AppServer, "SetTargets"); + + // We'll need to have a local copy of the chargingTargets that are referenced from updatedChargingTargetSchedules (which + // is a List where each ChargingTargetScheduleStruct has a List of ChargingTargetStructs). + // Note updatedChargingTargets only needs to exist for the duration of this method as once the new targets have been merged + // with the existing targets, we'll save the updated Targets structure to persistent storage and the reload it into + // mChargingTargetSchedulesList + ChargingTargetsMemMgr updatedChargingTargets; + + // Build up a new Targets structure + DataModel::DecodableList updatedChargingTargetSchedules; + + // updatedChargingTargetSchedules contains a List of ChargingTargetScheduleStruct where the memory of + // ChargingTargetScheduleStruct is which is allocated here. + Structs::ChargingTargetScheduleStruct::Type updatedChargingTargetSchedulesArray[kEvseTargetsMaxNumberOfDays]; + + // Iterate across the list of new schedules. For each schedule, iterate through the existing Target + // (mChargingTargetSchedulesList) working out how to merge the new schedule. + auto newIter = newChargingTargetSchedules.begin(); + while (newIter.Next()) + { + auto & newChargingTargetSchedule = newIter.GetValue(); + + uint8_t newBitmask = + newChargingTargetSchedule.dayOfWeekForSequence.GetField(static_cast(kAllTargetDaysMask)); + + ChipLogProgress(AppServer, "SetTargets: DayOfWeekForSequence = 0x%02x", newBitmask); + + PrintTargets(mChargingTargetSchedulesList); + + // Iterate across the existing schedule entries, seeing if there is overlap with + // the dayOfWeekForSequenceBitmap + bool found = false; + uint16_t updatedChargingTargetSchedulesIdx = 0; + + // Let the updatedChargingTargets object of the schedule index + updatedChargingTargets.PrepareDaySchedule(updatedChargingTargetSchedulesIdx); + + for (auto & currentChargingTargetSchedule : mChargingTargetSchedulesList) + { + uint8_t currentBitmask = + currentChargingTargetSchedule.dayOfWeekForSequence.GetField(static_cast(kAllTargetDaysMask)); + + ChipLogProgress(AppServer, "SetTargets: Scanning current entry %d of %d: bitmap 0x%02x", + updatedChargingTargetSchedulesIdx, static_cast(mChargingTargetSchedulesList.size()), + currentBitmask); + + // Work out if the new schedule dayOfWeekSequence overlaps with any existing schedules + uint8_t bitmaskA = static_cast(currentBitmask & newBitmask); + uint8_t bitmaskB = static_cast(currentBitmask & ~newBitmask); + + BitMask updatedBitmask; + + if (currentBitmask == bitmaskA) + { + // This entry has the all the same bits as the newEntry + updatedBitmask = BitMask(bitmaskA); + + // Copy the new chargingTargets to this schedule index + CHIP_ERROR err = updatedChargingTargets.AllocAndCopy(newChargingTargetSchedule.chargingTargets); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "SetTargets: Failed to copy the new chargingTargets %s", chip::ErrorStr(err)); + return err; + } + + found = true; + } + else + { + // This entry stays - but it has lost some days from the bitmask + updatedBitmask = BitMask(bitmaskB); + + // Copy the existing chargingTargets + CHIP_ERROR err = updatedChargingTargets.AllocAndCopy(currentChargingTargetSchedule.chargingTargets); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "SetTargets: Failed to copy the new chargingTargets %s", chip::ErrorStr(err)); + return err; + } + } + + // Update the new schedule with the dayOfWeekForSequence and list of chargingTargets + updatedChargingTargetSchedulesArray[updatedChargingTargetSchedulesIdx].dayOfWeekForSequence = updatedBitmask; + + updatedChargingTargetSchedulesArray[updatedChargingTargetSchedulesIdx].chargingTargets = + chip::app::DataModel::List( + updatedChargingTargets.GetChargingTargets(), updatedChargingTargets.GetNumDailyChargingTargets()); + + // Going to look at the next schedule entry + updatedChargingTargetSchedulesIdx++; + + // Let the updatedChargingTargets object of the schedule index + updatedChargingTargets.PrepareDaySchedule(updatedChargingTargetSchedulesIdx); + } + + // If found is false, then there were no existing entries for the dayOfWeekForSequence. Add a new entry + if (!found) + { + // Copy the new chargingTargets + CHIP_ERROR err = updatedChargingTargets.AllocAndCopy(newChargingTargetSchedule.chargingTargets); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "SetTargets: Failed to copy the new chargingTargets %s", chip::ErrorStr(err)); + return err; + } + + // Update the new schedule with the dayOfWeekForSequence and list of chargingTargets + updatedChargingTargetSchedulesArray[updatedChargingTargetSchedulesIdx].dayOfWeekForSequence = + newChargingTargetSchedule.dayOfWeekForSequence; + + updatedChargingTargetSchedulesArray[updatedChargingTargetSchedulesIdx].chargingTargets = + chip::app::DataModel::List( + updatedChargingTargets.GetChargingTargets(), updatedChargingTargets.GetNumDailyChargingTargets()); + + // We've added a new schedule entry + updatedChargingTargetSchedulesIdx++; + + // Let the updatedChargingTargets object of the schedule index + updatedChargingTargets.PrepareDaySchedule(updatedChargingTargetSchedulesIdx); + } + + // Now create the full Target data structure that we are going to save to persistent storage + DataModel::List updatedChargingTargetSchedulesList( + updatedChargingTargetSchedulesArray, updatedChargingTargetSchedulesIdx); + + CHIP_ERROR err = SaveTargets(updatedChargingTargetSchedulesList); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "SetTargets: Failed to save Target to persistent storage %s", chip::ErrorStr(err)); + return err; + } + + // Now reload from persistent storage so that mChargingTargetSchedulesList gets the update Target + err = LoadTargets(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "SetTargets: Failed to load Target from persistent storage %s", chip::ErrorStr(err)); + return err; + } + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +EvseTargetsDelegate::SaveTargets(DataModel::List & chargingTargetSchedulesList) +{ + uint16_t total = GetTlvSizeUpperBound(); + + Platform::ScopedMemoryBuffer backingBuffer; + ReturnErrorCodeIf(!backingBuffer.Calloc(total), CHIP_ERROR_NO_MEMORY); + TLV::ScopedBufferTLVWriter writer(std::move(backingBuffer), total); + + TLV::TLVType arrayType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, arrayType)); + for (auto & chargingTargetSchedule : chargingTargetSchedulesList) + { + ChipLogProgress( + AppServer, "SaveTargets: DayOfWeekForSequence = 0x%02x", + chargingTargetSchedule.dayOfWeekForSequence.GetField(static_cast(kAllTargetDaysMask))); + + TLV::TLVType evseTargetEntryType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, evseTargetEntryType)); + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(TargetEntryTag::kDayOfWeek), chargingTargetSchedule.dayOfWeekForSequence)); + + TLV::TLVType chargingTargetsListType; + ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(TargetEntryTag::kChargingTargetsList), TLV::kTLVType_List, + chargingTargetsListType)); + for (auto & chargingTarget : chargingTargetSchedule.chargingTargets) + { + TLV::TLVType chargingTargetsStructType = TLV::kTLVType_Structure; + ReturnErrorOnFailure(writer.StartContainer(TLV::ContextTag(TargetEntryTag::kChargingTargetsStruct), + TLV::kTLVType_Structure, chargingTargetsStructType)); + ReturnErrorOnFailure( + writer.Put(TLV::ContextTag(TargetEntryTag::kTargetTime), chargingTarget.targetTimeMinutesPastMidnight)); + if (chargingTarget.targetSoC.HasValue()) + { + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(TargetEntryTag::kTargetSoC), chargingTarget.targetSoC.Value())); + } + + if (chargingTarget.addedEnergy.HasValue()) + { + ReturnErrorOnFailure(writer.Put(TLV::ContextTag(TargetEntryTag::kAddedEnergy), chargingTarget.addedEnergy.Value())); + } + + ReturnErrorOnFailure(writer.EndContainer(chargingTargetsStructType)); + } + ReturnErrorOnFailure(writer.EndContainer(chargingTargetsListType)); + ReturnErrorOnFailure(writer.EndContainer(evseTargetEntryType)); + } + + ReturnErrorOnFailure(writer.EndContainer(arrayType)); + + uint64_t len = static_cast(writer.GetLengthWritten()); + ChipLogProgress(AppServer, "SaveTargets: length written 0x" ChipLogFormatX64, ChipLogValueX64(len)); + + writer.Finalize(backingBuffer); + + ReturnErrorOnFailure(mpTargetStore->SyncSetKeyValue(spEvseTargetsKeyName, backingBuffer.Get(), static_cast(len))); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR EvseTargetsDelegate::ClearTargets() +{ + /* We simply delete the data from the persistent store */ + mpTargetStore->SyncDeleteKeyValue(spEvseTargetsKeyName); + + // Now reload from persistent storage so that mChargingTargetSchedulesList gets updated (it will be empty) + CHIP_ERROR err = LoadTargets(); + + return err; +} + +void EvseTargetsDelegate::PrintTargets( + const DataModel::List & chargingTargetSchedules) +{ + ChipLogProgress(AppServer, "---------------------- TARGETS ---------------------"); + + uint16_t chargingTargetScheduleIdx = 0; + for (auto & chargingTargetSchedule : chargingTargetSchedules) + { + [[maybe_unused]] uint8_t bitmask = + chargingTargetSchedule.dayOfWeekForSequence.GetField(static_cast(kAllTargetDaysMask)); + ChipLogProgress(AppServer, "idx %u dayOfWeekForSequence 0x%02x", chargingTargetScheduleIdx, bitmask); + + uint16_t chargingTargetIdx = 0; + for (auto & chargingTarget : chargingTargetSchedule.chargingTargets) + { + [[maybe_unused]] int64_t addedEnergy = chargingTarget.addedEnergy.HasValue() ? chargingTarget.addedEnergy.Value() : 0; + + ChipLogProgress( + AppServer, "chargingTargetIdx %u targetTimeMinutesPastMidnight %u targetSoC %u addedEnergy 0x" ChipLogFormatX64, + chargingTargetIdx, chargingTarget.targetTimeMinutesPastMidnight, + chargingTarget.targetSoC.HasValue() ? chargingTarget.targetSoC.Value() : 0, ChipLogValueX64(addedEnergy)); + + chargingTargetIdx++; + } + + chargingTargetScheduleIdx++; + } +} + +/** + * Part of the FabricTable::Delegate interface. Gets called when a fabric is deleted, such as on FabricTable::Delete(). + **/ +void EvseTargetsDelegate::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) {} diff --git a/examples/energy-management-app/energy-management-common/tests/BUILD.gn b/examples/energy-management-app/energy-management-common/tests/BUILD.gn new file mode 100644 index 00000000000000..b55340a527233d --- /dev/null +++ b/examples/energy-management-app/energy-management-common/tests/BUILD.gn @@ -0,0 +1,44 @@ +# Copyright (c) 2020-2024 Project CHIP Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") + +import("${chip_root}/build/chip/chip_test_suite.gni") + +config("tests_config") { + include_dirs = [ "${chip_root}/examples/energy-management-app/energy-management-common/include" ] +} + +chip_test_suite("tests") { + output_name = "libEnergyTest" + output_dir = "${root_out_dir}/lib" + + public_configs = [ ":tests_config" ] + + test_sources = [ "TestEvseTargetsStorage.cpp" ] + + cflags = [ "-Wconversion" ] + + public_deps = [ + "${chip_root}/examples/energy-management-app/energy-management-common", + "${chip_root}/examples/energy-management-app/linux:test-evse-targets-store", + "${chip_root}/src/app", + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/app/tests:helpers", + "${chip_root}/src/lib", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support:testing", + ] +} diff --git a/examples/energy-management-app/energy-management-common/tests/TestEvseTargetsStorage.cpp b/examples/energy-management-app/energy-management-common/tests/TestEvseTargetsStorage.cpp new file mode 100644 index 00000000000000..6a1f2ec4c424f1 --- /dev/null +++ b/examples/energy-management-app/energy-management-common/tests/TestEvseTargetsStorage.cpp @@ -0,0 +1,219 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "EnergyEvseTargetsStore.h" + +using namespace chip; +using namespace chip::app; +using namespace chip::app::DataModel; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::EnergyEvse; + +namespace { + +constexpr uint16_t ENERGY_EVSE_SET_TARGETS_DAYS_IN_A_WEEK = 7; +constexpr uint16_t ENERGY_EVSE_SET_TARGETS_MAX_CHARGING_TARGETS = 10; + +class TestEvseTargetsStorage : public ::testing::Test +{ +public: + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } + + bool CompTargets(const DataModel::List & targets1, + const DataModel::List & targets2); + + void PopulateTargets(uint16_t numDays, uint16_t numChargingTargetsPerDay); + + void SetTargets(); + void CheckTargets(); + +private: + uint8_t mStore[4096]; + + EnergyEvse::Structs::ChargingTargetScheduleStruct::Type mChargingTargetSchedules[ENERGY_EVSE_SET_TARGETS_DAYS_IN_A_WEEK]; + EnergyEvse::Structs::ChargingTargetStruct::Type mChargingTargets[ENERGY_EVSE_SET_TARGETS_DAYS_IN_A_WEEK] + [ENERGY_EVSE_SET_TARGETS_MAX_CHARGING_TARGETS]; + chip::app::DataModel::List + mChargingTargetsList[ENERGY_EVSE_SET_TARGETS_DAYS_IN_A_WEEK]; + + chip::app::DataModel::List mRefChargingTargetSchedulesList; + DataModel::DecodableList mDecodableChargingTargetSchedulesList; + + TestPersistentStorageDelegate mStorageDelegate; + + EvseTargetsDelegate mEtd; + bool mEtdInitialised = false; +}; + +TEST_F(TestEvseTargetsStorage, TestEmpty) +{ + PopulateTargets(0, 0); + SetTargets(); + CheckTargets(); +} + +TEST_F(TestEvseTargetsStorage, TestFull) +{ + PopulateTargets(ENERGY_EVSE_SET_TARGETS_DAYS_IN_A_WEEK, ENERGY_EVSE_SET_TARGETS_MAX_CHARGING_TARGETS); + SetTargets(); + CheckTargets(); +} + +TEST_F(TestEvseTargetsStorage, TestPartial1) +{ + PopulateTargets(ENERGY_EVSE_SET_TARGETS_DAYS_IN_A_WEEK, 1); + SetTargets(); + CheckTargets(); +} + +TEST_F(TestEvseTargetsStorage, TestPartial2) +{ + PopulateTargets(1, ENERGY_EVSE_SET_TARGETS_MAX_CHARGING_TARGETS); + SetTargets(); + CheckTargets(); +} + +bool TestEvseTargetsStorage::CompTargets(const DataModel::List & targets1, + const DataModel::List & targets2) +{ + if (targets1.size() != targets2.size()) + { + ChipLogError(AppServer, "CompTargets: Different number of ChargingTargetScheduleStruct in lists"); + return false; + } + + uint16_t dayIdx = 0; + for (const auto & entry1 : targets1) + { + const auto & entry2 = targets2[dayIdx++]; + + if (entry1.dayOfWeekForSequence != entry2.dayOfWeekForSequence) + { + ChipLogError(AppServer, "CompTargets: Different dayOfWeekForSequence"); + return false; + } + + if (entry1.chargingTargets.size() != entry2.chargingTargets.size()) + { + ChipLogError(AppServer, "CompTargets: Different number of chargingTargets in day list"); + return false; + } + + uint16_t chargingTargetsIdx = 0; + for (const auto & targetStruct1 : entry1.chargingTargets) + { + const auto & targetStruct2 = entry2.chargingTargets[chargingTargetsIdx++]; + + if (targetStruct1.targetTimeMinutesPastMidnight != targetStruct2.targetTimeMinutesPastMidnight) + { + ChipLogError(AppServer, "CompTargets: Different targetTimeMinutesPastMidnight"); + return false; + } + + if (targetStruct1.targetSoC != targetStruct2.targetSoC) + { + ChipLogError(AppServer, "CompTargets: Different targetSoC"); + return false; + } + + if (targetStruct1.addedEnergy != targetStruct2.addedEnergy) + { + ChipLogError(AppServer, "CompTargets: Different addedEnergy"); + return false; + } + } + } + + return true; +} + +void TestEvseTargetsStorage::PopulateTargets(uint16_t numDays, uint16_t numChargingTargetsPerDay) +{ + for (uint16_t dayIdx = 0; dayIdx < numDays; dayIdx++) + { + for (uint16_t chargingTargetIdx = 0; chargingTargetIdx < numChargingTargetsPerDay; chargingTargetIdx++) + { + mChargingTargets[dayIdx][chargingTargetIdx].targetTimeMinutesPastMidnight = + static_cast(dayIdx * 60 + chargingTargetIdx); + mChargingTargets[dayIdx][chargingTargetIdx].targetSoC.SetValue(65); + mChargingTargets[dayIdx][chargingTargetIdx].addedEnergy.SetValue(400); + } + + mChargingTargetsList[dayIdx] = chip::app::DataModel::List( + mChargingTargets[dayIdx], numChargingTargetsPerDay); + + mChargingTargetSchedules[dayIdx].dayOfWeekForSequence.Set(static_cast(1 << dayIdx)); + mChargingTargetSchedules[dayIdx].chargingTargets = mChargingTargetsList[dayIdx]; + } + + chip::app::DataModel::List chargingTargetSchedulesList( + mChargingTargetSchedules, numDays); + + mRefChargingTargetSchedulesList = chargingTargetSchedulesList; + + TLV::TLVReader mReader; + TLV::TLVWriter mWriter; + + mWriter.Init(mStore, sizeof(mStore)); + + CHIP_ERROR err = DataModel::Encode(mWriter, TLV::AnonymousTag(), mRefChargingTargetSchedulesList); + EXPECT_EQ(err, CHIP_NO_ERROR); + + EXPECT_EQ(mWriter.Finalize(), CHIP_NO_ERROR); + + mReader.Init(mStore); + EXPECT_EQ(mReader.Next(), CHIP_NO_ERROR); + + err = DataModel::Decode(mReader, mDecodableChargingTargetSchedulesList); + EXPECT_EQ(err, CHIP_NO_ERROR); +} + +void TestEvseTargetsStorage::SetTargets() +{ + if (!mEtdInitialised) + { + mEtd.Init(&mStorageDelegate); + + mEtdInitialised = true; + } + + CHIP_ERROR err = mEtd.SetTargets(mDecodableChargingTargetSchedulesList); + EXPECT_EQ(err, CHIP_NO_ERROR); +} + +void TestEvseTargetsStorage::CheckTargets() +{ + const DataModel::List targets = mEtd.GetTargets(); + + EXPECT_TRUE(CompTargets(mRefChargingTargetSchedulesList, targets)); +} + +} // namespace diff --git a/examples/energy-management-app/linux/BUILD.gn b/examples/energy-management-app/linux/BUILD.gn index c3564c345a1182..1a6e2d287fed2e 100644 --- a/examples/energy-management-app/linux/BUILD.gn +++ b/examples/energy-management-app/linux/BUILD.gn @@ -18,8 +18,6 @@ import("${chip_root}/build/chip/tools.gni") import("${chip_root}/src/app/common_flags.gni") import("${chip_root}/third_party/imgui/imgui.gni") -assert(chip_build_tools) - import("${chip_root}/examples/common/pigweed/pigweed_rpcs.gni") if (chip_enable_pw_rpc) { @@ -36,6 +34,7 @@ config("includes") { executable("chip-energy-management-app") { sources = [ + "${chip_root}/examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/DEMTestEventTriggers.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", @@ -45,6 +44,7 @@ executable("chip-energy-management-app") { "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseEventTriggers.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/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyReportingEventTriggers.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/FakeReadings.cpp", @@ -127,6 +127,23 @@ executable("chip-energy-management-app") { output_dir = root_out_dir } +source_set("test-evse-targets-store") { + sources = [ + "${chip_root}/examples/energy-management-app/energy-management-common/src/ChargingTargetsMemMgr.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp", + ] + + include_dirs = [ + "include", + "${chip_root}/examples/energy-management-app/energy-management-common/include", + ] + + deps = [ + "${chip_root}/examples/energy-management-app/energy-management-common", + "${chip_root}/src/lib", + ] +} + group("linux") { deps = [ ":chip-energy-management-app" ] } diff --git a/examples/energy-management-app/linux/main.cpp b/examples/energy-management-app/linux/main.cpp index 2d056e56a9b149..cdc2c4ed53d844 100644 --- a/examples/energy-management-app/linux/main.cpp +++ b/examples/energy-management-app/linux/main.cpp @@ -39,7 +39,7 @@ constexpr uint16_t kOptionFeatureMap = 'f'; // Define the chip::ArgParser command line structures for extending the command line to support the // -f/--featureMap option static chip::ArgParser::OptionDef sFeatureMapOptionDefs[] = { - { "featureSet", chip::ArgParser::kArgumentRequired, kOptionFeatureMap }, { NULL } + { "featureSet", chip::ArgParser::kArgumentRequired, kOptionFeatureMap }, { nullptr } }; static chip::ArgParser::OptionSet sCmdLineOptions = { @@ -74,11 +74,11 @@ static uint32_t ParseNumber(const char * pString) uint32_t num = 0; if (strlen(pString) > 2 && pString[0] == '0' && pString[1] == 'x') { - num = (uint32_t) strtoul(&pString[2], NULL, 16); + num = (uint32_t) strtoul(&pString[2], nullptr, 16); } else { - num = (uint32_t) strtoul(pString, NULL, 10); + num = (uint32_t) strtoul(pString, nullptr, 10); } return num; diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index cd79d2b42e3273..f8a5334d78c47d 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -84,6 +84,8 @@ source_set("app-main") { ":energy-evse-test-event-trigger", ":energy-reporting-test-event-trigger", ":smco-test-event-trigger", + "${chip_root}/src/controller:controller", + "${chip_root}/src/controller:gen_check_chip_controller_headers", "${chip_root}/src/lib", "${chip_root}/src/platform/logging:stdio", ] @@ -140,7 +142,11 @@ source_set("commissioner-main") { defines += [ "ENABLE_CHIP_SHELL" ] } - public_deps = [ "${chip_root}/src/lib" ] + public_deps = [ + "${chip_root}/src/controller:controller", + "${chip_root}/src/controller:gen_check_chip_controller_headers", + "${chip_root}/src/lib", + ] deps = [ "${chip_root}/src/app/server" ] if (chip_enable_transport_trace) { diff --git a/examples/shell/shell_common/BUILD.gn b/examples/shell/shell_common/BUILD.gn index 0e318ccb31a5ca..470f81b5d2459a 100644 --- a/examples/shell/shell_common/BUILD.gn +++ b/examples/shell/shell_common/BUILD.gn @@ -70,12 +70,14 @@ static_library("shell_common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/smco-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", "${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/ChargingTargetsMemMgr.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/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/EnergyEvseManager.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseTargetsStore.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyTimeUtils.cpp", ] diff --git a/src/BUILD.gn b/src/BUILD.gn index 7e5fedcc0d539b..c162a61f0620fa 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -155,6 +155,15 @@ if (chip_build_tests) { } } + chip_test_group("example_tests") { + deps = [] + tests = [] + if (chip_device_platform != "esp32" && chip_device_platform != "efr32" && + current_os != "android") { + tests += [ "${chip_root}/examples/energy-management-app/energy-management-common/tests" ] + } + } + chip_test_group("fake_platform_tests") { tests = [ "${chip_root}/src/lib/dnssd/platform/tests" ] } 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 f5c8c281615435..cdf480b64cdde4 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 @@ -25,6 +25,7 @@ #include #include #include +#include using chip::Protocols::InteractionModel::Status; 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 8204a271434e63..53c741d9378cc4 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 @@ -21,7 +21,6 @@ #include #include -#include #include namespace chip { @@ -36,8 +35,6 @@ class Delegate void SetEndpointId(EndpointId aEndpoint) { mEndpointId = aEndpoint; } - using HarmonicMeasurementIterator = CommonIterator; - virtual PowerModeEnum GetPowerMode() = 0; virtual uint8_t GetNumberOfMeasurementTypes() = 0; diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerHandler.h b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerHandler.h index 307156e21f1197..38db739bc97f32 100644 --- a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerHandler.h +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerHandler.h @@ -57,6 +57,8 @@ enum class EnergyEvseTrigger : uint64_t kEVChargeDemand = 0x0099000000000004, // EV Charge Demand Test Event Clear | Simulate the EV becoming fully charged kEVChargeDemandClear = 0x0099000000000005, + // EV Charge TimeOfUse Mode | Simulate putting the EVSE into a Mode with the TimeOfUse tag included + kEVTimeOfUseMode = 0x0099000000000006, // EVSE has a GroundFault fault kEVSEGroundFault = 0x0099000000000010, // EVSE has a OverTemperature fault @@ -65,6 +67,8 @@ enum class EnergyEvseTrigger : uint64_t kEVSEFaultClear = 0x0099000000000012, // EVSE Diagnostics Complete | Simulate diagnostics have been completed and return to normal kEVSEDiagnosticsComplete = 0x0099000000000020, + // EV Charge TimeOfUse Mode clear | Simulate clearing the EVSE Mode TimeOfUse tag + kEVTimeOfUseModeClear = 0x0099000000000021, }; class EnergyEvseTestEventTriggerHandler : public TestEventTriggerHandler diff --git a/src/app/clusters/energy-evse-server/energy-evse-server.cpp b/src/app/clusters/energy-evse-server/energy-evse-server.cpp index af825217e2565a..4807bf33969681 100644 --- a/src/app/clusters/energy-evse-server/energy-evse-server.cpp +++ b/src/app/clusters/energy-evse-server/energy-evse-server.cpp @@ -309,13 +309,13 @@ void Instance::HandleEnableCharging(HandlerContext & ctx, const Commands::Enable auto & minimumChargeCurrent = commandData.minimumChargeCurrent; auto & maximumChargeCurrent = commandData.maximumChargeCurrent; - if ((minimumChargeCurrent < kMinimumChargeCurrent) || (minimumChargeCurrent > kMaximumChargeCurrent)) + if (minimumChargeCurrent < kMinimumChargeCurrent) { ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); return; } - if ((maximumChargeCurrent < kMinimumChargeCurrent) || (maximumChargeCurrent > kMaximumChargeCurrent)) + if (maximumChargeCurrent < kMinimumChargeCurrent) { ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); return; @@ -339,7 +339,7 @@ void Instance::HandleEnableDischarging(HandlerContext & ctx, const Commands::Ena auto & dischargingEnabledUntil = commandData.dischargingEnabledUntil; auto & maximumDischargeCurrent = commandData.maximumDischargeCurrent; - if ((maximumDischargeCurrent < kMinimumChargeCurrent) || (maximumDischargeCurrent > kMaximumChargeCurrent)) + if (maximumDischargeCurrent < kMinimumChargeCurrent) { ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); return; @@ -362,27 +362,136 @@ void Instance::HandleStartDiagnostics(HandlerContext & ctx, const Commands::Star void Instance::HandleSetTargets(HandlerContext & ctx, const Commands::SetTargets::DecodableType & commandData) { // Call the delegate - // TODO - // Status status = mDelegate.SetTargets(); - Status status = Status::UnsupportedCommand; + auto & chargingTargetSchedules = commandData.chargingTargetSchedules; + + Status status = ValidateTargets(chargingTargetSchedules); + if (status != Status::Success) + { + ChipLogError(AppServer, "SetTargets contained invalid data - Rejecting"); + } + else + { + status = mDelegate.SetTargets(chargingTargetSchedules); + } ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); } + +Status Instance::ValidateTargets( + const DataModel::DecodableList & chargingTargetSchedules) +{ + /* A) check that the targets are valid + * 1) each target must be within valid range (TargetTimeMinutesPastMidnight < 1440) + * 2) each target must be within valid range (TargetSoC percent 0 - 100) + * If SOC feature not supported then this MUST be 100 or not present + * 3) each target must be within valid range (AddedEnergy >= 0) + * B) Day of Week is only allowed to be included once + */ + + uint8_t dayOfWeekBitmap = 0; + + auto iter = chargingTargetSchedules.begin(); + while (iter.Next()) + { + auto & entry = iter.GetValue(); + uint8_t bitmask = entry.dayOfWeekForSequence.GetField(static_cast(0x7F)); + ChipLogProgress(AppServer, "DayOfWeekForSequence = 0x%02x", bitmask); + + if ((dayOfWeekBitmap & bitmask) != 0) + { + // A bit has already been set - Return ConstraintError + ChipLogError(AppServer, "DayOfWeekForSequence has a bit set which has already been set in another entry."); + return Status::ConstraintError; + } + dayOfWeekBitmap |= bitmask; // add this day Of week to the previously seen days + + auto iterInner = entry.chargingTargets.begin(); + uint8_t innerIdx = 0; + while (iterInner.Next()) + { + auto & targetStruct = iterInner.GetValue(); + uint16_t minutesPastMidnight = targetStruct.targetTimeMinutesPastMidnight; + ChipLogProgress(AppServer, "[%d] MinutesPastMidnight : %d", innerIdx, + static_cast(minutesPastMidnight)); + + if (minutesPastMidnight > 1439) + { + ChipLogError(AppServer, "MinutesPastMidnight has invalid value (%d)", static_cast(minutesPastMidnight)); + return Status::ConstraintError; + } + + // If SocReporting is supported, targetSoc must have a value in the range [0, 100] + if (HasFeature(Feature::kSoCReporting)) + { + if (!targetStruct.targetSoC.HasValue()) + { + ChipLogError(AppServer, "kSoCReporting is supported but TargetSoC does not have a value"); + return Status::Failure; + } + + if (targetStruct.targetSoC.Value() > 100) + { + ChipLogError(AppServer, "TargetSoC has invalid value (%d)", static_cast(targetStruct.targetSoC.Value())); + return Status::ConstraintError; + } + } + else if (targetStruct.targetSoC.HasValue() && targetStruct.targetSoC.Value() != 100) + { + // If SocReporting is not supported but targetSoc has a value, it must be 100 + ChipLogError(AppServer, "TargetSoC has can only be 100%% if SOC feature is not supported"); + return Status::ConstraintError; + } + + // One or both of targetSoc and addedEnergy must be specified + if (!(targetStruct.targetSoC.HasValue()) && !(targetStruct.addedEnergy.HasValue())) + { + ChipLogError(AppServer, "Must have one of AddedEnergy or TargetSoC"); + return Status::Failure; + } + + // Validate the value of addedEnergy, if specified is >= 0 + if (targetStruct.addedEnergy.HasValue() && targetStruct.addedEnergy.Value() < 0) + { + ChipLogError(AppServer, "AddedEnergy has invalid value (%ld)", + static_cast(targetStruct.addedEnergy.Value())); + return Status::ConstraintError; + } + innerIdx++; + } + + if (iterInner.GetStatus() != CHIP_NO_ERROR) + { + return Status::InvalidCommand; + } + } + + if (iter.GetStatus() != CHIP_NO_ERROR) + { + return Status::InvalidCommand; + } + + return Status::Success; +} + void Instance::HandleGetTargets(HandlerContext & ctx, const Commands::GetTargets::DecodableType & commandData) { - // Call the delegate - // TODO - // Status status = mDelegate.GetTargets(); - Status status = Status::UnsupportedCommand; + Commands::GetTargetsResponse::Type response; - ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); + Status status = mDelegate.GetTargets(response.chargingTargetSchedules); + if (status != Status::Success) + { + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); + return; + } + + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Protocols::InteractionModel::Status::Success); } + void Instance::HandleClearTargets(HandlerContext & ctx, const Commands::ClearTargets::DecodableType & commandData) { // Call the delegate - // TODO - // Status status = mDelegate.ClearTargets(); - Status status = Status::UnsupportedCommand; + Status status = mDelegate.ClearTargets(); ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); } diff --git a/src/app/clusters/energy-evse-server/energy-evse-server.h b/src/app/clusters/energy-evse-server/energy-evse-server.h index 2ff46f339ff6ad..979e44c040a17b 100644 --- a/src/app/clusters/energy-evse-server/energy-evse-server.h +++ b/src/app/clusters/energy-evse-server/energy-evse-server.h @@ -36,8 +36,9 @@ namespace EnergyEvse { // Spec-defined constraints constexpr int64_t kMinimumChargeCurrent = 0; -constexpr int64_t kMaximumChargeCurrent = 80000; constexpr uint32_t kMaxRandomizationDelayWindow = 86400; +constexpr uint8_t kEvseTargetsMaxNumberOfDays = 7; +constexpr uint8_t kEvseTargetsMaxTargetsPerDay = 10; /** @brief * Defines methods for implementing application-specific logic for the EVSE Management Cluster. @@ -81,6 +82,36 @@ class Delegate */ virtual Protocols::InteractionModel::Status StartDiagnostics() = 0; + /** + * @brief Delegate should implement a handler for the SetTargets command. + * It should report Status::Success if successful and may + * return other Status codes if it fails + */ + virtual Protocols::InteractionModel::Status + SetTargets(const DataModel::DecodableList & chargingTargetSchedules) = 0; + + /** + * @brief Delegate should implement a handler for LoadTargets + * + * This needs to load any stored targets into memory + */ + virtual Protocols::InteractionModel::Status LoadTargets() = 0; + + /** + * @brief Delegate should implement a handler for GetTargets + * + * @param[out] The full targets structure + */ + virtual Protocols::InteractionModel::Status + GetTargets(DataModel::List & chargingTargetSchedules) = 0; + + /** + * @brief Delegate should implement a handler for ClearTargets command. + * It should report Status::Success if successful and may + * return other Status codes if it fails + */ + virtual Protocols::InteractionModel::Status ClearTargets() = 0; + // ------------------------------------------------------------------ // Get attribute methods virtual StateEnum GetState() = 0; @@ -178,6 +209,10 @@ class Instance : public AttributeAccessInterface, public CommandHandlerInterface void HandleSetTargets(HandlerContext & ctx, const Commands::SetTargets::DecodableType & commandData); void HandleGetTargets(HandlerContext & ctx, const Commands::GetTargets::DecodableType & commandData); void HandleClearTargets(HandlerContext & ctx, const Commands::ClearTargets::DecodableType & commandData); + + // Check that the targets are valid + Protocols::InteractionModel::Status + ValidateTargets(const DataModel::DecodableList & chargingTargetSchedules); }; } // namespace EnergyEvse diff --git a/src/python_testing/TC_EEVSE_2_3.py b/src/python_testing/TC_EEVSE_2_3.py new file mode 100644 index 00000000000000..d8d8f8fdfb96b5 --- /dev/null +++ b/src/python_testing/TC_EEVSE_2_3.py @@ -0,0 +1,454 @@ +# +# 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. + +# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments +# for details about the block below. +# +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${ENERGY_MANAGEMENT_APP} +# test-runner-run/run1/factoryreset: True +# test-runner-run/run1/quiet: True +# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json --enable-key 000102030405060708090a0b0c0d0e0f +# test-runner-run/run1/script-args: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f --endpoint 1 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === + +import logging +from datetime import datetime, timedelta, timezone + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from chip.interaction_model import Status +from matter_testing_support import EventChangeCallback, MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EEVSE_Utils import EEVSEBaseTestHelper + +logger = logging.getLogger(__name__) + + +class TC_EEVSE_2_3(MatterBaseTest, EEVSEBaseTestHelper): + + def desc_TC_EEVSE_2_3(self) -> str: + """Returns a description of this test""" + return "5.1.4. [TC-EEVSE-2.3] Optional ChargingPreferences feature functionality with DUT as Server\n" \ + "This test case verifies the primary functionality of the Energy EVSE cluster server with the optional ChargingPreferences feature supported." + + def pics_TC_EEVSE_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 ["EEVSE.S", "EEVSE.S.F00"] + + def steps_TC_EEVSE_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 value is 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event.", + "Verify Command response is Success"), + TestStep("4", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EVSE TimeOfUse Mode Test Event.", + "Verify Command response is Success"), + TestStep("5", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event.", + "Verify Command response is Success and event EEVSE.S.E00(EVConnected) sent"), + TestStep("6", "TH sends ClearTargets.", + "Verify Command response is Success"), + TestStep("6a", "TH reads NextChargeStartTime attribute.", + "Verify value is null."), + TestStep("6b", "TH reads NextChargeTargetTime attribute.", + "Verify value is null."), + TestStep("6c", "TH reads NextChargeRequiredEnergy attribute.", + "Verify value is null."), + TestStep("6d", "TH reads NextChargeTargetSoC attribute.", + "Verify value is null."), + TestStep("7", "TH sends GetTargets.", + "Response EEVSE.S.C00.Tx(GetTargetsResponse) sent with no targets defined."), + TestStep("8", "TH sends SetTargets with DayOfTheWeekforSequence=0x7F (i.e. having all days set) and a single ChargingTargets={TargetTime=1439, TargetSoC=null, AddedEnergy=25000000}.", + "Verify Command response is Success"), + TestStep("8a", "TH reads NextChargeStartTime attribute.", + "Verify value is null."), + TestStep("8b", "TH reads NextChargeTargetTime attribute.", + "Verify value is null."), + TestStep("8c", "TH reads NextChargeRequiredEnergy attribute.", + "Verify value is null."), + TestStep("8d", "TH reads NextChargeTargetSoC attribute.", + "Verify value is null."), + TestStep("9", "TH sends EnableCharging with ChargingEnabledUntil=null, minimumChargeCurrent=6000, maximumChargeCurrent=60000.", + "Verify Command response is Success"), + TestStep("9a", "TH reads NextChargeStartTime attribute.", + "Verify value is before the next TargetTime above."), + TestStep("9b", "TH reads NextChargeTargetTime attribute.", + "Verify value is TargetTime above."), + TestStep("9c", "TH reads NextChargeRequiredEnergy attribute.", + "Verify value is AddedEnergy above."), + TestStep("9d", "TH reads NextChargeTargetSoC attribute.", + "Verify value is null."), + TestStep("10", "TH sends GetTargets.", + "Response EEVSE.S.C00.Tx(GetTargetsResponse) sent with targets equivalent to the above (Note 1)."), + TestStep("11", "TH sends SetTargets with DayOfTheWeekforSequence=0x7F (i.e. having all days set) and a single ChargingTargets={TargetTime=1, TargetSoC=100, AddedEnergy=null}.", + "Verify Command response is Success"), + TestStep("11a", "TH reads NextChargeStartTime attribute.", + "Verify value is before the next TargetTime above."), + TestStep("11b", "TH reads NextChargeTargetTime attribute.", + "Verify value is TargetTime above."), + TestStep("11c", "TH reads NextChargeRequiredEnergy attribute.", + "Verify value is null."), + TestStep("11d", "TH reads NextChargeTargetSoC attribute.", + "Verify value is 100."), + TestStep("12", "TH sends GetTargets.", + "Response EEVSE.S.C00.Tx(GetTargetsResponse) sent with targets equivalent to the above (Note 1)."), + TestStep("13", "TH sends SetTargets with DayOfTheWeekforSequence=0x40 (i.e. having Saturday set) and 10 ChargingTargets with TargetTimes=60,180,300,420,540,660,780,900,1020,1140 and all with TargetSoC=null, AddedEnergy=2500000.", + "Verify Command response is Success"), + TestStep("14", "TH sends SetTargets with DayOfTheWeekforSequence=0x01 (i.e. having Sunday set) and no ChargingTargets.", + "Verify Command response is Success"), + TestStep("15", "TH sends GetTargets.", + "Response EEVSE.S.C00.Tx(GetTargetsResponse) sent with 1 target for each day Monday to Friday equivalent to step 9 (Note 1), 10 targets for Saturday as step 11, and no targets for Sunday."), + TestStep("16", "TH sends ClearTargets.", + "Verify Command response is Success"), + TestStep("16a", "TH reads NextChargeStartTime attribute.", + "Verify value is null."), + TestStep("16b", "TH reads NextChargeTargetTime attribute.", + "Verify value is null."), + TestStep("16c", "TH reads NextChargeRequiredEnergy attribute.", + "Verify value is null."), + TestStep("16d", "TH reads NextChargeTargetSoC attribute.", + "Verify value is null."), + TestStep("17", "TH sends GetTargets.", + "Response EEVSE.S.C00.Tx(GetTargetsResponse) sent with no targets defined."), + TestStep("18", "TH sends SetTargets with two identical ChargingTargetSchedules={DayOfTheWeekforSequence=0x01,ChargingTarget[0]={TargetTime=60,TargetSoC=null,AddedEnergy=2500000}}.", + "Verify Command response is ConstraintError"), + TestStep("19", "TH sends SetTargets with DayOfTheWeekforSequence=0x40 and 11 ChargingTargets with TargetTimes=60,180,300,420,540,660,780,900,1020,1140,1260 and all with TargetSoC=null, AddedEnergy=2500000.", + "Verify Command response is ResourceExhausted"), + TestStep("20", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for EV Plugged-in Test Event Clear.", + "Verify Command response is Success and event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("21", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event Clear.", + "Verify Command response is Success"), + ] + + return steps + + def log_get_targets_response(self, get_targets_response): + logger.info(f" Rx'd: {get_targets_response}") + for index, entry in enumerate(get_targets_response.chargingTargetSchedules): + logger.info( + f" [{index}] DayOfWeekForSequence: {entry.dayOfWeekForSequence:02x}") + for sub_index, sub_entry in enumerate(entry.chargingTargets): + logger.info( + f" - [{sub_index}] TargetTime: {sub_entry.targetTimeMinutesPastMidnight} TargetSoC: {sub_entry.targetSoC} AddedEnergy: {sub_entry.addedEnergy}") + + def convert_epoch_s_to_time(self, epoch_s, tz=timezone.utc): + delta_from_epoch = timedelta(seconds=epoch_s) + matter_epoch = datetime(2000, 1, 1, 0, 0, 0, 0, tz) + + return matter_epoch + delta_from_epoch + + def compute_expected_target_time_as_epoch_s(self, minutes_past_midnight): + """Takes minutes past midnight and assumes local timezone, returns the value in Matter Epoch_S""" + # Matter epoch is 0 hours, 0 minutes, 0 seconds on Jan 1, 2000 UTC + # Get the current midnight + minutesPastMidnight as epoch_s + # NOTE that MinutesPastMidnight is in LOCAL time not UTC so it reflects + # the charging time based on where the consumer is. + target_time = datetime.now() # Get time in local time + target_time = target_time.replace(hour=int(minutes_past_midnight / 60), + minute=(minutes_past_midnight % 60), second=0, + microsecond=0) # Align to minutes past midnight + + if (target_time < datetime.now()): + # This is in the past - so we need to add 1 day + # We can get away with this in this test scenario - but should + # really look at the next target on this day to see if that is in the future + target_time = target_time + timedelta(days=1) + + # Shift to UTC so we can use timezone aware subtraction from Matter epoch in UTC + target_time = target_time.astimezone(timezone.utc) + + logger.info( + f"minutesPastMidnight = {minutes_past_midnight} => " + f"{int(minutes_past_midnight/60)}:{int(minutes_past_midnight%60)}" + f" Expected target_time = {target_time}") + + target_time_delta = target_time - \ + datetime(2000, 1, 1, 0, 0, 0, 0).astimezone(timezone.utc) + expected_target_time_epoch_s = int(target_time_delta.total_seconds()) + return expected_target_time_epoch_s + + @async_test_body + async def test_TC_EEVSE_2_3(self): + + self.step("1") + # Commission DUT - already done + + # Subscribe to Events and when they are sent push them to a queue for checking later + events_callback = EventChangeCallback(Clusters.EnergyEvse) + await events_callback.start(self.default_controller, + self.dut_node_id, + self.matter_test_config.endpoint) + + self.step("2") + await self.check_test_event_triggers_enabled() + + self.step("3") + await self.send_test_event_trigger_basic() + + self.step("4") + await self.send_test_event_trigger_time_of_use_mode() + + self.step("5") + await self.send_test_event_trigger_pluggedin() + event_data = events_callback.wait_for_event_report( + Clusters.EnergyEvse.Events.EVConnected) + session_id = event_data.sessionID + + self.step("6") + await self.send_clear_targets_command() + + self.step("6a") + await self.check_evse_attribute("NextChargeStartTime", NullValue) + + self.step("6b") + await self.check_evse_attribute("NextChargeTargetTime", NullValue) + + self.step("6c") + await self.check_evse_attribute("NextChargeRequiredEnergy", NullValue) + + self.step("6d") + await self.check_evse_attribute("NextChargeTargetSoC", NullValue) + + self.step("7") + get_targets_response = await self.send_get_targets_command() + self.log_get_targets_response(get_targets_response) + empty_targets_response = Clusters.EnergyEvse.Commands.GetTargetsResponse( + chargingTargetSchedules=[]) + asserts.assert_equal(get_targets_response, empty_targets_response, + f"Unexpected 'GetTargets' response value - expected {empty_targets_response}, was {get_targets_response}") + + self.step("8") + # The targets is a list of up to 7x ChargingTargetScheduleStruct's (one per day) + # each containing a list of up to 10x targets per day + minutes_past_midnight = 1439 + dailyTargets = [Clusters.EnergyEvse.Structs.ChargingTargetStruct(targetTimeMinutesPastMidnight=minutes_past_midnight, + # targetSoc not sent + addedEnergy=25000000)] + targets = [Clusters.EnergyEvse.Structs.ChargingTargetScheduleStruct( + dayOfWeekForSequence=0x7F, chargingTargets=dailyTargets)] + # This should be all days Sun-Sat (0x7F) with an TargetTime 1439 and added Energy 25kWh + await self.send_set_targets_command(chargingTargetSchedules=targets) + + self.step("8a") + await self.check_evse_attribute("NextChargeStartTime", NullValue) + + self.step("8b") + await self.check_evse_attribute("NextChargeTargetTime", NullValue) + + self.step("8c") + await self.check_evse_attribute("NextChargeRequiredEnergy", NullValue) + + self.step("8d") + await self.check_evse_attribute("NextChargeTargetSoC", NullValue) + + self.step("9") + await self.send_enable_charge_command(charge_until=NullValue, min_charge=6000, max_charge=60000) + + self.step("9a") + next_start_time_epoch_s = await self.read_evse_attribute_expect_success(attribute="NextChargeStartTime") + + expected_next_start_time_epoch_s = self.compute_expected_target_time_as_epoch_s( + minutes_past_midnight) + asserts.assert_less(next_start_time_epoch_s, + expected_next_start_time_epoch_s) + + self.step("9b") + await self.check_evse_attribute("NextChargeTargetTime", expected_next_start_time_epoch_s) + + self.step("9c") + await self.check_evse_attribute("NextChargeRequiredEnergy", 25000000) + + self.step("9d") + await self.check_evse_attribute("NextChargeTargetSoC", NullValue) + + self.step("10") + get_targets_response = await self.send_get_targets_command() + self.log_get_targets_response(get_targets_response) + asserts.assert_equal(get_targets_response.chargingTargetSchedules, targets, + f"Unexpected 'GetTargets' response value - expected {targets}, was {get_targets_response}") + + self.step("11") + # This should be all days Sun-Sat (0x7F) with an TargetTime 1 and SoC of 100%, AddedEnergy= NullValue + minutes_past_midnight = 1 + daily_targets_step_11 = [Clusters.EnergyEvse.Structs.ChargingTargetStruct(targetTimeMinutesPastMidnight=minutes_past_midnight, + targetSoC=100)] + targets_step_11 = [Clusters.EnergyEvse.Structs.ChargingTargetScheduleStruct( + dayOfWeekForSequence=0x7F, chargingTargets=daily_targets_step_11)] + + await self.send_set_targets_command(chargingTargetSchedules=targets_step_11) + + self.step("11a") + next_start_time_epoch_s = await self.read_evse_attribute_expect_success(attribute="NextChargeStartTime") + logger.info( + f"Received NextChargeStartTime: {next_start_time_epoch_s} = {self.convert_epoch_s_to_time(next_start_time_epoch_s, tz=None)}") + + self.step("11b") + next_target_time_epoch_s = await self.read_evse_attribute_expect_success(attribute="NextChargeTargetTime") + logger.info( + f"Received NextChargeTargetTime: {next_target_time_epoch_s} = {self.convert_epoch_s_to_time(next_target_time_epoch_s, tz=None)}") + + # This should be the next MinutesPastMidnight converted to realtime as epoch_s + expected_target_time_epoch_s = self.compute_expected_target_time_as_epoch_s( + minutes_past_midnight) + + asserts.assert_less(next_start_time_epoch_s, next_target_time_epoch_s, + f"Unexpected 'NextChargeStartTime' response value - expected this to be < {next_target_time_epoch_s}, was {next_start_time_epoch_s}") + + asserts.assert_equal(next_target_time_epoch_s, expected_target_time_epoch_s, + f"Unexpected 'NextChargeTargetTime' response value - expected {expected_target_time_epoch_s} = {self.convert_epoch_s_to_time(expected_target_time_epoch_s, tz=None)}, was {next_target_time_epoch_s} = {self.convert_epoch_s_to_time(next_target_time_epoch_s, tz=None)}") + + self.step("11c") + await self.check_evse_attribute("NextChargeRequiredEnergy", NullValue) + + self.step("11d") + await self.check_evse_attribute("NextChargeTargetSoC", 100) + + self.step("12") + get_targets_response = await self.send_get_targets_command() + self.log_get_targets_response(get_targets_response) + # This should be the same as targets_step_11 + asserts.assert_equal(get_targets_response.chargingTargetSchedules, targets_step_11, + f"Unexpected 'GetTargets' response value - expected {targets_step_11}, was {get_targets_response}") + + self.step("13") + # This should modify Sat (0x40) with 10 targets throughout the day + daily_targets_step_13 = [ + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=60, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=180, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=300, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=420, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=540, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=660, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=780, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=900, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=1020, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=1140, addedEnergy=25000000), + ] + targets_step_13 = [Clusters.EnergyEvse.Structs.ChargingTargetScheduleStruct( + dayOfWeekForSequence=0x40, chargingTargets=daily_targets_step_13)] + await self.send_set_targets_command(chargingTargetSchedules=targets_step_13) + + self.step("14") + # This should modify Sun (0x01) with NO targets on that day + daily_targets_step_14 = [] + targets_step_14 = [Clusters.EnergyEvse.Structs.ChargingTargetScheduleStruct( + dayOfWeekForSequence=0x01, chargingTargets=daily_targets_step_14)] + await self.send_set_targets_command(chargingTargetSchedules=targets_step_14) + + self.step("15") + get_targets_response = await self.send_get_targets_command() + self.log_get_targets_response(get_targets_response) + # We should expect that there should be 3 entries: + # [0] This should be all days (except Sun & Sat) = 0x3e with an TargetTime 1439 and added Energy 25kWh (from step 9) + # [1] This should be (Sat) = 0x40 with 10 TargetTimes and added Energy 25kWh (from step 11) + # [2] This should be (Sun) = 0x01 with NO Targets (from step 12) + # TODO - it would be better to iterate through each day and check it matches + asserts.assert_equal(len(get_targets_response.chargingTargetSchedules), 3, + "'GetTargets' response should have 3 entries") + asserts.assert_equal(get_targets_response.chargingTargetSchedules[0].dayOfWeekForSequence, 0x3e, + "'GetTargets' response entry 0 should have DayOfWeekForSequence = 0x3e (62)") + asserts.assert_equal(get_targets_response.chargingTargetSchedules[0].chargingTargets, daily_targets_step_11, + f"'GetTargets' response entry 0 should have chargingTargets = {daily_targets_step_11})") + asserts.assert_equal(get_targets_response.chargingTargetSchedules[1], targets_step_13[0], + f"'GetTargets' response entry 1 should be {targets_step_13})") + asserts.assert_equal(get_targets_response.chargingTargetSchedules[2], targets_step_14[0], + f"'GetTargets' response entry 2 should be {targets_step_14})") + + self.step("16") + await self.send_clear_targets_command() + + self.step("16a") + await self.check_evse_attribute("NextChargeStartTime", NullValue) + + self.step("16b") + await self.check_evse_attribute("NextChargeTargetTime", NullValue) + + self.step("16c") + await self.check_evse_attribute("NextChargeRequiredEnergy", NullValue) + + self.step("16d") + await self.check_evse_attribute("NextChargeTargetSoC", NullValue) + + self.step("17") + get_targets_response = await self.send_get_targets_command() + self.log_get_targets_response(get_targets_response) + asserts.assert_equal(get_targets_response, empty_targets_response, + f"Unexpected 'GetTargets' response value - expected {empty_targets_response}, was {get_targets_response}") + + self.step("18") + daily_targets_step_18 = [Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=60, addedEnergy=25000000)] + targets_step_18 = [Clusters.EnergyEvse.Structs.ChargingTargetScheduleStruct(dayOfWeekForSequence=0x1, chargingTargets=daily_targets_step_18), + Clusters.EnergyEvse.Structs.ChargingTargetScheduleStruct(dayOfWeekForSequence=0x1, chargingTargets=daily_targets_step_18)] + await self.send_set_targets_command(chargingTargetSchedules=targets_step_18, expected_status=Status.ConstraintError) + + self.step("19") + daily_targets_step_19 = [ + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=60, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=180, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=300, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=420, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=540, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=660, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=780, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=900, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=1020, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=1140, addedEnergy=25000000), + Clusters.EnergyEvse.Structs.ChargingTargetStruct( + targetTimeMinutesPastMidnight=1260, addedEnergy=25000000), + ] + + targets_step_19 = [Clusters.EnergyEvse.Structs.ChargingTargetScheduleStruct( + dayOfWeekForSequence=0x40, chargingTargets=daily_targets_step_19)] + await self.send_set_targets_command(chargingTargetSchedules=targets_step_19, expected_status=Status.ResourceExhausted) + + self.step("20") + await self.send_test_event_trigger_pluggedin_clear() + event_data = events_callback.wait_for_event_report( + Clusters.EnergyEvse.Events.EVNotDetected) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand + self.validate_ev_not_detected_event( + event_data, session_id, expected_state, expected_duration=0, expected_charged=0) + + self.step("21") + await self.send_test_event_trigger_basic_clear() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EEVSE_Utils.py b/src/python_testing/TC_EEVSE_Utils.py index 6b6b1395072ba0..17f64191ed37ec 100644 --- a/src/python_testing/TC_EEVSE_Utils.py +++ b/src/python_testing/TC_EEVSE_Utils.py @@ -16,8 +16,10 @@ import logging +import typing import chip.clusters as Clusters +from chip.clusters.Types import NullValue from chip.interaction_model import InteractionModelError, Status from mobly import asserts @@ -36,6 +38,22 @@ async def check_evse_attribute(self, attribute, expected_value, endpoint: int = asserts.assert_equal(value, expected_value, f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") + 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_evse_attribute_in_range(self, attribute, lower_value: int, upper_value: int, endpoint: int = None, allow_null: bool = False): + value = await self.read_evse_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_energy_evse_attributes(self, endpoint: int = None): return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") @@ -45,7 +63,8 @@ async def write_user_max_charge(self, endpoint: int = None, user_max_charge: int result = await self.default_controller.WriteAttribute(self.dut_node_id, [(endpoint, Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent(user_max_charge))]) - asserts.assert_equal(result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") + asserts.assert_equal( + result[0].Status, Status.Success, "UserMaximumChargeCurrent write failed") async def send_enable_charge_command(self, endpoint: int = None, charge_until: int = None, timedRequestTimeoutMs: int = 3000, min_charge: int = 6000, max_charge: int = 32000, expected_status: Status = Status.Success): @@ -58,7 +77,8 @@ async def send_enable_charge_command(self, endpoint: int = None, charge_until: i timedRequestTimeoutMs=timedRequestTimeoutMs) except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + asserts.assert_equal(e.status, expected_status, + "Unexpected error returned") async def send_disable_command(self, endpoint: int = None, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): try: @@ -67,7 +87,8 @@ async def send_disable_command(self, endpoint: int = None, timedRequestTimeoutMs timedRequestTimeoutMs=timedRequestTimeoutMs) except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + asserts.assert_equal(e.status, expected_status, + "Unexpected error returned") async def send_start_diagnostics_command(self, endpoint: int = None, timedRequestTimeoutMs: int = 3000, expected_status: Status = Status.Success): @@ -77,7 +98,46 @@ async def send_start_diagnostics_command(self, endpoint: int = None, timedReques timedRequestTimeoutMs=timedRequestTimeoutMs) except InteractionModelError as e: - asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + asserts.assert_equal(e.status, expected_status, + "Unexpected error returned") + + async def send_clear_targets_command(self, endpoint: int = None, timedRequestTimeoutMs: int = 3000, + expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.ClearTargets(), + endpoint=endpoint, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, + "Unexpected error returned") + + async def send_get_targets_command(self, endpoint: int = None, timedRequestTimeoutMs: int = 3000, + expected_status: Status = Status.Success): + try: + get_targets_resp = await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.GetTargets(), + endpoint=endpoint, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, + "Unexpected error returned") + + return get_targets_resp + + async def send_set_targets_command(self, endpoint: int = None, + chargingTargetSchedules: typing.List[ + Clusters.EnergyEvse.Structs.ChargingTargetScheduleStruct] = None, + timedRequestTimeoutMs: int = 3000, + expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.SetTargets(chargingTargetSchedules), + endpoint=endpoint, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, + "Unexpected error returned") async def send_test_event_trigger_basic(self): await self.send_test_event_triggers(eventTrigger=0x0099000000000000) @@ -97,6 +157,9 @@ async def send_test_event_trigger_charge_demand(self): async def send_test_event_trigger_charge_demand_clear(self): await self.send_test_event_triggers(eventTrigger=0x0099000000000005) + async def send_test_event_trigger_time_of_use_mode(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000006) + async def send_test_event_trigger_evse_ground_fault(self): await self.send_test_event_triggers(eventTrigger=0x0099000000000010)