diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index 44470a612f42c7..07f3df6b7a9822 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -519,8 +519,10 @@ EthyleneOxideConcentrationMeasurement EvalCode EvalCodeWithName EvalFrameDefault +EV EVB evk +EVSE exceptfds ExchangeContext exe diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index b12b11b747b92e..ad7cb56245f2bd 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -3920,6 +3920,185 @@ provisional cluster ElectricalEnergyMeasurement = 145 { readonly attribute int16u clusterRevision = 65533; } +/** This cluster allows a client to manage the power draw of a device. An example of such a client could be an Energy Management System (EMS) which controls an Energy Smart Appliance (ESA). */ +provisional cluster DeviceEnergyManagement = 152 { + revision 2; + + enum CauseEnum : enum8 { + kNormalCompletion = 0; + kOffline = 1; + kFault = 2; + kUserOptOut = 3; + } + + enum CostTypeEnum : enum8 { + kFinancial = 0; + kGHGEmissions = 1; + kComfort = 2; + kTemperature = 3; + } + + enum ESAStateEnum : enum8 { + kOffline = 0; + kOnline = 1; + kFault = 2; + kUserOptOut = 3; + kPowerAdjustActive = 4; + kPaused = 5; + } + + enum ESATypeEnum : enum8 { + kEVSE = 0; + kSpaceHeating = 1; + kWaterHeating = 2; + kSpaceCooling = 3; + kSpaceHeatingCooling = 4; + kBatteryStorage = 5; + kSolarPV = 6; + kFridgeFreezer = 7; + kWashingMachine = 8; + kDishwasher = 9; + kCooking = 10; + kHomeWaterPump = 11; + kIrrigationWaterPump = 12; + kPoolPump = 13; + kOther = 255; + } + + bitmap Feature : bitmap32 { + kPowerAdjustment = 0x1; + kPowerForecastReporting = 0x2; + kStateForecastReporting = 0x4; + kForecastAdjustment = 0x8; + } + + struct CostStruct { + CostTypeEnum costType = 0; + int32s value = 1; + int8u decimalPoints = 2; + optional int16u currency = 3; + } + + struct SlotStruct { + elapsed_s minDuration = 0; + elapsed_s maxDuration = 1; + elapsed_s defaultDuration = 2; + elapsed_s elapsedSlotTime = 3; + elapsed_s remainingSlotTime = 4; + boolean slotIsPauseable = 5; + elapsed_s minPauseDuration = 6; + elapsed_s maxPauseDuration = 7; + optional int16u manufacturerESAState = 8; + optional power_mw nominalPower = 9; + optional power_mw minPower = 10; + optional power_mw maxPower = 11; + optional energy_mwh nominalEnergy = 12; + optional CostStruct costs[] = 13; + optional power_mw minPowerAdjustment = 14; + optional power_mw maxPowerAdjustment = 15; + optional elapsed_s minDurationAdjustment = 16; + optional elapsed_s maxDurationAdjustment = 17; + } + + struct ForecastStruct { + int16u forecastId = 0; + nullable int16u activeSlotNumber = 1; + epoch_s startTime = 2; + epoch_s endTime = 3; + optional nullable epoch_s earliestStartTime = 4; + optional epoch_s latestEndTime = 5; + boolean isPauseable = 6; + SlotStruct slots[] = 7; + } + + struct ConstraintsStruct { + epoch_s startTime = 0; + elapsed_s duration = 1; + optional power_mw nominalPower = 2; + optional energy_mwh maximumEnergy = 3; + optional int8s loadControl = 4; + } + + struct PowerAdjustStruct { + power_mw minPower = 0; + power_mw maxPower = 1; + elapsed_s minDuration = 2; + elapsed_s maxDuration = 3; + } + + struct SlotAdjustmentStruct { + int8u slotIndex = 0; + power_mw nominalPower = 1; + elapsed_s duration = 2; + } + + info event PowerAdjustStart = 0 { + } + + info event PowerAdjustEnd = 1 { + CauseEnum cause = 0; + elapsed_s duration = 1; + energy_mwh energyUse = 2; + } + + info event Paused = 2 { + } + + info event Resumed = 3 { + } + + readonly attribute ESATypeEnum ESAType = 0; + readonly attribute boolean ESACanGenerate = 1; + readonly attribute ESAStateEnum ESAState = 2; + readonly attribute power_mw absMinPower = 3; + readonly attribute power_mw absMaxPower = 4; + readonly attribute optional nullable PowerAdjustStruct powerAdjustmentCapability[] = 5; + readonly attribute optional nullable ForecastStruct forecast = 6; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct PowerAdjustRequestRequest { + power_mw power = 0; + elapsed_s duration = 1; + } + + request struct StartTimeAdjustRequestRequest { + epoch_s requestedStartTime = 0; + } + + request struct PauseRequestRequest { + elapsed_s duration = 0; + } + + request struct ModifyForecastRequestRequest { + int32u forecastId = 0; + SlotAdjustmentStruct slotAdjustments[] = 1; + } + + request struct RequestConstraintBasedForecastRequest { + ConstraintsStruct constraints[] = 0; + } + + /** Allows a client to request an adjustment in the power consumption of an ESA for a specified duration. */ + command PowerAdjustRequest(PowerAdjustRequestRequest): DefaultSuccess = 0; + /** Allows a client to cancel an ongoing PowerAdjustmentRequest operation. */ + command CancelPowerAdjustRequest(): DefaultSuccess = 1; + /** Allows a client to adjust the start time of a Forecast sequence that has not yet started operation (i.e. where the current Forecast StartTime is in the future). */ + command StartTimeAdjustRequest(StartTimeAdjustRequestRequest): DefaultSuccess = 2; + /** Allows a client to temporarily pause an operation and reduce the ESAs energy demand. */ + command PauseRequest(PauseRequestRequest): DefaultSuccess = 3; + /** Allows a client to cancel the PauseRequest command and enable earlier resumption of operation. */ + command ResumeRequest(): DefaultSuccess = 4; + /** Allows a client to modify a Forecast within the limits allowed by the ESA. */ + command ModifyForecastRequest(ModifyForecastRequestRequest): DefaultSuccess = 5; + /** Allows a client to ask the ESA to recompute its Forecast based on power and time constraints. */ + command RequestConstraintBasedForecast(RequestConstraintBasedForecastRequest): DefaultSuccess = 6; +} + /** Electric Vehicle Supply Equipment (EVSE) is equipment used to charge an Electric Vehicle (EV) or Plug-In Hybrid Electric Vehicle. This cluster provides an interface to the functionality of Electric Vehicle Supply Equipment (EVSE) management. */ provisional cluster EnergyEvse = 153 { revision 2; @@ -7737,25 +7916,53 @@ endpoint 1 { ram attribute clusterRevision default = 1; } + server cluster DeviceEnergyManagement { + emits event PowerAdjustStart; + emits event PowerAdjustEnd; + emits event Paused; + emits event Resumed; + callback attribute ESAType; + callback attribute ESACanGenerate; + callback attribute ESAState; + callback attribute absMinPower; + callback attribute absMaxPower; + callback attribute powerAdjustmentCapability; + callback attribute forecast; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + ram attribute clusterRevision default = 2; + + handle command PowerAdjustRequest; + handle command CancelPowerAdjustRequest; + handle command StartTimeAdjustRequest; + handle command PauseRequest; + handle command ResumeRequest; + handle command ModifyForecastRequest; + handle command RequestConstraintBasedForecast; + } + server cluster EnergyEvse { - callback attribute state default = 0; - callback attribute supplyState default = 0; - callback attribute faultState default = 0; - callback attribute chargingEnabledUntil default = 0; - callback attribute dischargingEnabledUntil default = 0; - callback attribute circuitCapacity default = 0; - callback attribute minimumChargeCurrent default = 6000; - callback attribute maximumChargeCurrent default = 0; - callback attribute maximumDischargeCurrent default = 0; - callback attribute userMaximumChargeCurrent default = 0; - callback attribute randomizationDelayWindow default = 600; + callback attribute state; + callback attribute supplyState; + callback attribute faultState; + callback attribute chargingEnabledUntil; + callback attribute dischargingEnabledUntil; + callback attribute circuitCapacity; + callback attribute minimumChargeCurrent; + callback attribute maximumChargeCurrent; + callback attribute maximumDischargeCurrent; + callback attribute userMaximumChargeCurrent; + callback attribute randomizationDelayWindow; callback attribute numberOfWeeklyTargets default = 0; callback attribute numberOfDailyTargets default = 1; callback attribute nextChargeStartTime; callback attribute nextChargeTargetTime; callback attribute nextChargeRequiredEnergy; callback attribute nextChargeTargetSoC; - callback attribute approximateEVEfficiency default = 0xFFFF; + callback attribute approximateEVEfficiency; callback attribute stateOfCharge; callback attribute batteryCapacity; callback attribute vehicleID; @@ -7767,7 +7974,7 @@ endpoint 1 { callback attribute acceptedCommandList; callback attribute eventList; callback attribute attributeList; - ram attribute featureMap default = 0; + callback attribute featureMap; ram attribute clusterRevision default = 2; handle command GetTargetsResponse; diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index a24d3db39a2b65..9013ea3140e6db 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -12640,6 +12640,313 @@ } ] }, + { + "name": "Device Energy Management", + "code": 152, + "mfgCode": null, + "define": "DEVICE_ENERGY_MANAGEMENT_CLUSTER", + "side": "server", + "enabled": 1, + "apiMaturity": "provisional", + "commands": [ + { + "name": "PowerAdjustRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CancelPowerAdjustRequest", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "StartTimeAdjustRequest", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "PauseRequest", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ResumeRequest", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ModifyForecastRequest", + "code": 5, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RequestConstraintBasedForecast", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "ESAType", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "ESATypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ESACanGenerate", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ESAState", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "ESAStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AbsMinPower", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "power_mw", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AbsMaxPower", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "power_mw", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PowerAdjustmentCapability", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Forecast", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "ForecastStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "PowerAdjustStart", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "PowerAdjustEnd", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "Paused", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "Resumed", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, { "name": "Energy EVSE", "code": 153, @@ -13186,7 +13493,7 @@ "side": "server", "type": "bitmap32", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, "defaultValue": "0", diff --git a/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp b/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp new file mode 100644 index 00000000000000..385c1cfa43c8ee --- /dev/null +++ b/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp @@ -0,0 +1,73 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::DeviceEnergyManagement; + +static std::unique_ptr gDelegate; +static std::unique_ptr gInstance; + +void emberAfDeviceEnergyManagementClusterInitCallback(chip::EndpointId endpointId) +{ + VerifyOrDie(endpointId == 1); // this cluster is only enabled for endpoint 1. + VerifyOrDie(gInstance == nullptr); + + CHIP_ERROR err; + + if (gDelegate || gInstance) + { + ChipLogError(AppServer, "DEM Instance or Delegate already exist."); + return; + } + + gDelegate = std::make_unique(); + if (!gDelegate) + { + ChipLogError(AppServer, "Failed to allocate memory for DeviceEnergyManagementDelegate"); + return; + } + + /* Manufacturer may optionally not support all features, commands & attributes */ + gInstance = std::make_unique( + endpointId, *gDelegate, + BitMask( + DeviceEnergyManagement::Feature::kPowerForecastReporting, DeviceEnergyManagement::Feature::kStateForecastReporting, + DeviceEnergyManagement::Feature::kPowerAdjustment, DeviceEnergyManagement::Feature::kForecastAdjustment), + BitMask(OptionalCommands::kSupportsModifyForecastRequest, + OptionalCommands::kSupportsRequestConstraintBasedForecast)); + + if (!gInstance) + { + ChipLogError(AppServer, "Failed to allocate memory for DeviceEnergyManagementManager"); + gDelegate.reset(); + return; + } + + err = gInstance->Init(); /* Register Attribute & Command handlers */ + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Init failed on gInstance"); + gInstance.reset(); + gDelegate.reset(); + return; + } +} 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 a0773de104c187..f9983d47883852 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 @@ -22,25 +22,26 @@ using namespace chip; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; -static EnergyEvseDelegate * gDelegate = nullptr; -static EnergyEvseManager * gInstance = nullptr; +static std::unique_ptr gDelegate; +static std::unique_ptr gInstance; void emberAfEnergyEvseClusterInitCallback(chip::EndpointId endpointId) { VerifyOrDie(endpointId == 1); // this cluster is only enabled for endpoint 1. - VerifyOrDie(gInstance == nullptr); - gDelegate = new EnergyEvseDelegate(); - if (gDelegate != nullptr) + VerifyOrDie(!gInstance); + + gDelegate = std::make_unique(); + if (gDelegate) { - gInstance = - new EnergyEvseManager(endpointId, *gDelegate, - BitMask( - EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, - EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, EnergyEvse::Feature::kV2x), - BitMask(OptionalAttributes::kSupportsUserMaximumChargingCurrent, - OptionalAttributes::kSupportsRandomizationWindow, - OptionalAttributes::kSupportsApproximateEvEfficiency), - BitMask(OptionalCommands::kSupportsStartDiagnostics)); + gInstance = std::make_unique( + endpointId, *gDelegate, + BitMask(EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, + EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, + EnergyEvse::Feature::kV2x), + BitMask(OptionalAttributes::kSupportsUserMaximumChargingCurrent, + OptionalAttributes::kSupportsRandomizationWindow, + OptionalAttributes::kSupportsApproximateEvEfficiency), + BitMask(OptionalCommands::kSupportsStartDiagnostics)); gInstance->Init(); /* Register Attribute & Command handlers */ } diff --git a/examples/all-clusters-app/ameba/chip_main.cmake b/examples/all-clusters-app/ameba/chip_main.cmake index e68398afdf625d..66528a9941141a 100755 --- a/examples/all-clusters-app/ameba/chip_main.cmake +++ b/examples/all-clusters-app/ameba/chip_main.cmake @@ -156,6 +156,7 @@ list( ${chip_dir}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp ${chip_dir}/examples/all-clusters-app/all-clusters-common/src/air-quality-instance.cpp ${chip_dir}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp + ${chip_dir}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp ${chip_dir}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp ${chip_dir}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp ${chip_dir}/examples/all-clusters-app/all-clusters-common/src/oven-modes.cpp @@ -175,6 +176,8 @@ list( ${chip_dir}/examples/all-clusters-app/ameba/main/ManualOperationCommand.cpp ${chip_dir}/examples/all-clusters-app/ameba/main/SmokeCOAlarmManager.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/EnergyEvseDelegateImpl.cpp ${chip_dir}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp diff --git a/examples/all-clusters-app/asr/BUILD.gn b/examples/all-clusters-app/asr/BUILD.gn index 1c23617af66262..263bd8cf7734b7 100755 --- a/examples/all-clusters-app/asr/BUILD.gn +++ b/examples/all-clusters-app/asr/BUILD.gn @@ -74,6 +74,7 @@ asr_executable("clusters_app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/air-quality-instance.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/oven-modes.cpp", @@ -81,6 +82,8 @@ 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/DeviceEnergyManagementDelegateImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${examples_plat_dir}/ButtonHandler.cpp", diff --git a/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn b/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn index 26c0f67e389a0c..6ec6202ea21209 100644 --- a/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn +++ b/examples/all-clusters-app/cc13x2x7_26x2x7/BUILD.gn @@ -79,6 +79,7 @@ ti_simplelink_executable("all-clusters-app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/oven-modes.cpp", @@ -86,6 +87,8 @@ 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/DeviceEnergyManagementDelegateImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${chip_root}/examples/providers/DeviceInfoProviderImpl.cpp", diff --git a/examples/all-clusters-app/cc13x4_26x4/BUILD.gn b/examples/all-clusters-app/cc13x4_26x4/BUILD.gn index e9492d7a223f42..02dbebf7e6d8c9 100644 --- a/examples/all-clusters-app/cc13x4_26x4/BUILD.gn +++ b/examples/all-clusters-app/cc13x4_26x4/BUILD.gn @@ -82,6 +82,7 @@ ti_simplelink_executable("all-clusters-app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/oven-modes.cpp", @@ -89,6 +90,8 @@ 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/DeviceEnergyManagementDelegateImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${chip_root}/examples/providers/DeviceInfoProviderImpl.cpp", diff --git a/examples/all-clusters-app/esp32/main/CMakeLists.txt b/examples/all-clusters-app/esp32/main/CMakeLists.txt index 89ce7c1c872160..623e2ded547abe 100644 --- a/examples/all-clusters-app/esp32/main/CMakeLists.txt +++ b/examples/all-clusters-app/esp32/main/CMakeLists.txt @@ -75,6 +75,7 @@ set(SRC_DIRS_LIST "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/icd-management-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/diagnostic-logs-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/occupancy-sensor-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/device-energy-management-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/energy-evse-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/ethernet-network-diagnostics-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/localization-configuration-server" diff --git a/examples/all-clusters-app/infineon/psoc6/BUILD.gn b/examples/all-clusters-app/infineon/psoc6/BUILD.gn index f4469b9c7bc290..f2b9ab939d6f38 100644 --- a/examples/all-clusters-app/infineon/psoc6/BUILD.gn +++ b/examples/all-clusters-app/infineon/psoc6/BUILD.gn @@ -110,6 +110,7 @@ psoc6_executable("clusters_app") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/air-quality-instance.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/oven-modes.cpp", @@ -117,6 +118,8 @@ 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/DeviceEnergyManagementDelegateImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${examples_plat_dir}/LEDWidget.cpp", diff --git a/examples/all-clusters-app/linux/BUILD.gn b/examples/all-clusters-app/linux/BUILD.gn index b2cc122b5090ab..8b031d769773e4 100644 --- a/examples/all-clusters-app/linux/BUILD.gn +++ b/examples/all-clusters-app/linux/BUILD.gn @@ -26,6 +26,7 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/boolcfg-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/dishwasher-alarm-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/dishwasher-mode.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp", @@ -42,6 +43,8 @@ source_set("chip-all-clusters-common") { "${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/all-clusters-app/all-clusters-common/src/tcc-mode.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/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "AllClustersCommandDelegate.cpp", diff --git a/examples/all-clusters-app/mbed/CMakeLists.txt b/examples/all-clusters-app/mbed/CMakeLists.txt index 28104e85f0c3b1..f2b6d9f5e88d27 100644 --- a/examples/all-clusters-app/mbed/CMakeLists.txt +++ b/examples/all-clusters-app/mbed/CMakeLists.txt @@ -65,6 +65,7 @@ target_sources(${APP_TARGET} PRIVATE ${ALL_CLUSTERS_COMMON}/src/air-quality-instance.cpp ${ALL_CLUSTERS_COMMON}/src/concentration-measurement-instances.cpp ${ALL_CLUSTERS_COMMON}/src/fan-stub.cpp + ${ALL_CLUSTERS_COMMON}/src/device-energy-management-stub.cpp ${ALL_CLUSTERS_COMMON}/src/energy-evse-stub.cpp ${ALL_CLUSTERS_COMMON}/src/oven-modes.cpp ${ALL_CLUSTERS_COMMON}/src/resource-monitoring-delegates.cpp @@ -73,6 +74,9 @@ target_sources(${APP_TARGET} PRIVATE ${ALL_CLUSTERS_COMMON}/src/static-supported-temperature-levels.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseManager.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementDelegateImpl.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementManager.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 48499c29712d40..49bf4fb7f95e3a 100644 --- a/examples/all-clusters-app/nrfconnect/CMakeLists.txt +++ b/examples/all-clusters-app/nrfconnect/CMakeLists.txt @@ -65,10 +65,13 @@ target_sources(app PRIVATE ${ALL_CLUSTERS_COMMON_DIR}/src/fan-stub.cpp ${ALL_CLUSTERS_COMMON_DIR}/src/oven-modes.cpp ${ALL_CLUSTERS_COMMON_DIR}/src/energy-evse-stub.cpp + ${ALL_CLUSTERS_COMMON_DIR}/src/device-energy-management-stub.cpp ${ALL_CLUSTERS_COMMON_DIR}/src/binding-handler.cpp ${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/DeviceEnergyManagementDelegateImpl.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementManager.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyEvseManager.cpp ${NRFCONNECT_COMMON}/util/LEDWidget.cpp) diff --git a/examples/all-clusters-app/nxp/mw320/BUILD.gn b/examples/all-clusters-app/nxp/mw320/BUILD.gn index 01bba26479a7af..a858c18e87fed2 100644 --- a/examples/all-clusters-app/nxp/mw320/BUILD.gn +++ b/examples/all-clusters-app/nxp/mw320/BUILD.gn @@ -78,6 +78,7 @@ mw320_executable("shell_mw320") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/air-quality-instance.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/oven-modes.cpp", @@ -85,6 +86,8 @@ 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/DeviceEnergyManagementDelegateImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", "${chip_root}/src/lib/shell/streamer_mw320.cpp", diff --git a/examples/all-clusters-app/openiotsdk/CMakeLists.txt b/examples/all-clusters-app/openiotsdk/CMakeLists.txt index 60b5bdddf40740..3b50f286b1683b 100644 --- a/examples/all-clusters-app/openiotsdk/CMakeLists.txt +++ b/examples/all-clusters-app/openiotsdk/CMakeLists.txt @@ -60,12 +60,15 @@ target_sources(${APP_TARGET} ${ALL_CLUSTERS_COMMON}/src/concentration-measurement-instances.cpp ${ALL_CLUSTERS_COMMON}/src/fan-stub.cpp ${ALL_CLUSTERS_COMMON}/src/oven-modes.cpp + ${ALL_CLUSTERS_COMMON}/src/device-energy-management-stub.cpp ${ALL_CLUSTERS_COMMON}/src/energy-evse-stub.cpp ${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/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON}/src/EnergyEvseManager.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementDelegateImpl.cpp + ${ENERGY_MANAGEMENT_COMMON}/src/DeviceEnergyManagementManager.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 3571869e8949aa..ae715a8c41ebfb 100644 --- a/examples/all-clusters-app/telink/CMakeLists.txt +++ b/examples/all-clusters-app/telink/CMakeLists.txt @@ -82,10 +82,13 @@ 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/fan-stub.cpp + ${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/EnergyEvseDelegateImpl.cpp ${ENERGY_MANAGEMENT_COMMON_DIR}/src/EnergyEvseManager.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementDelegateImpl.cpp + ${ENERGY_MANAGEMENT_COMMON_DIR}/src/DeviceEnergyManagementManager.cpp ${TELINK_COMMON}/common/src/mainCommon.cpp ${TELINK_COMMON}/common/src/AppTaskCommon.cpp ${TELINK_COMMON}/util/src/LEDWidget.cpp diff --git a/examples/all-clusters-app/tizen/BUILD.gn b/examples/all-clusters-app/tizen/BUILD.gn index 202d288f87053c..e9bb7f56e4bfaa 100644 --- a/examples/all-clusters-app/tizen/BUILD.gn +++ b/examples/all-clusters-app/tizen/BUILD.gn @@ -27,6 +27,7 @@ source_set("chip-all-clusters-common") { "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/concentration-measurement-instances.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/fan-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/oven-modes.cpp", @@ -34,6 +35,8 @@ 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/DeviceEnergyManagementDelegateImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", ] diff --git a/examples/all-clusters-minimal-app/esp32/main/CMakeLists.txt b/examples/all-clusters-minimal-app/esp32/main/CMakeLists.txt index cb38515a1a8770..cae8b64315a232 100644 --- a/examples/all-clusters-minimal-app/esp32/main/CMakeLists.txt +++ b/examples/all-clusters-minimal-app/esp32/main/CMakeLists.txt @@ -81,6 +81,7 @@ set(SRC_DIRS_LIST "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/diagnostic-logs-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/door-lock-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/occupancy-sensor-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/device-energy-management-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/energy-evse-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/ethernet-network-diagnostics-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/localization-configuration-server" diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.matter b/examples/energy-management-app/energy-management-common/energy-management-app.matter index d48a22c42cc51e..753c428234a038 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.matter +++ b/examples/energy-management-app/energy-management-common/energy-management-app.matter @@ -900,6 +900,185 @@ cluster GroupKeyManagement = 63 { fabric command access(invoke: administer) KeySetReadAllIndices(): KeySetReadAllIndicesResponse = 4; } +/** This cluster allows a client to manage the power draw of a device. An example of such a client could be an Energy Management System (EMS) which controls an Energy Smart Appliance (ESA). */ +provisional cluster DeviceEnergyManagement = 152 { + revision 2; + + enum CauseEnum : enum8 { + kNormalCompletion = 0; + kOffline = 1; + kFault = 2; + kUserOptOut = 3; + } + + enum CostTypeEnum : enum8 { + kFinancial = 0; + kGHGEmissions = 1; + kComfort = 2; + kTemperature = 3; + } + + enum ESAStateEnum : enum8 { + kOffline = 0; + kOnline = 1; + kFault = 2; + kUserOptOut = 3; + kPowerAdjustActive = 4; + kPaused = 5; + } + + enum ESATypeEnum : enum8 { + kEVSE = 0; + kSpaceHeating = 1; + kWaterHeating = 2; + kSpaceCooling = 3; + kSpaceHeatingCooling = 4; + kBatteryStorage = 5; + kSolarPV = 6; + kFridgeFreezer = 7; + kWashingMachine = 8; + kDishwasher = 9; + kCooking = 10; + kHomeWaterPump = 11; + kIrrigationWaterPump = 12; + kPoolPump = 13; + kOther = 255; + } + + bitmap Feature : bitmap32 { + kPowerAdjustment = 0x1; + kPowerForecastReporting = 0x2; + kStateForecastReporting = 0x4; + kForecastAdjustment = 0x8; + } + + struct CostStruct { + CostTypeEnum costType = 0; + int32s value = 1; + int8u decimalPoints = 2; + optional int16u currency = 3; + } + + struct SlotStruct { + elapsed_s minDuration = 0; + elapsed_s maxDuration = 1; + elapsed_s defaultDuration = 2; + elapsed_s elapsedSlotTime = 3; + elapsed_s remainingSlotTime = 4; + boolean slotIsPauseable = 5; + elapsed_s minPauseDuration = 6; + elapsed_s maxPauseDuration = 7; + optional int16u manufacturerESAState = 8; + optional power_mw nominalPower = 9; + optional power_mw minPower = 10; + optional power_mw maxPower = 11; + optional energy_mwh nominalEnergy = 12; + optional CostStruct costs[] = 13; + optional power_mw minPowerAdjustment = 14; + optional power_mw maxPowerAdjustment = 15; + optional elapsed_s minDurationAdjustment = 16; + optional elapsed_s maxDurationAdjustment = 17; + } + + struct ForecastStruct { + int16u forecastId = 0; + nullable int16u activeSlotNumber = 1; + epoch_s startTime = 2; + epoch_s endTime = 3; + optional nullable epoch_s earliestStartTime = 4; + optional epoch_s latestEndTime = 5; + boolean isPauseable = 6; + SlotStruct slots[] = 7; + } + + struct ConstraintsStruct { + epoch_s startTime = 0; + elapsed_s duration = 1; + optional power_mw nominalPower = 2; + optional energy_mwh maximumEnergy = 3; + optional int8s loadControl = 4; + } + + struct PowerAdjustStruct { + power_mw minPower = 0; + power_mw maxPower = 1; + elapsed_s minDuration = 2; + elapsed_s maxDuration = 3; + } + + struct SlotAdjustmentStruct { + int8u slotIndex = 0; + power_mw nominalPower = 1; + elapsed_s duration = 2; + } + + info event PowerAdjustStart = 0 { + } + + info event PowerAdjustEnd = 1 { + CauseEnum cause = 0; + elapsed_s duration = 1; + energy_mwh energyUse = 2; + } + + info event Paused = 2 { + } + + info event Resumed = 3 { + } + + readonly attribute ESATypeEnum ESAType = 0; + readonly attribute boolean ESACanGenerate = 1; + readonly attribute ESAStateEnum ESAState = 2; + readonly attribute power_mw absMinPower = 3; + readonly attribute power_mw absMaxPower = 4; + readonly attribute optional nullable PowerAdjustStruct powerAdjustmentCapability[] = 5; + readonly attribute optional nullable ForecastStruct forecast = 6; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct PowerAdjustRequestRequest { + power_mw power = 0; + elapsed_s duration = 1; + } + + request struct StartTimeAdjustRequestRequest { + epoch_s requestedStartTime = 0; + } + + request struct PauseRequestRequest { + elapsed_s duration = 0; + } + + request struct ModifyForecastRequestRequest { + int32u forecastId = 0; + SlotAdjustmentStruct slotAdjustments[] = 1; + } + + request struct RequestConstraintBasedForecastRequest { + ConstraintsStruct constraints[] = 0; + } + + /** Allows a client to request an adjustment in the power consumption of an ESA for a specified duration. */ + command PowerAdjustRequest(PowerAdjustRequestRequest): DefaultSuccess = 0; + /** Allows a client to cancel an ongoing PowerAdjustmentRequest operation. */ + command CancelPowerAdjustRequest(): DefaultSuccess = 1; + /** Allows a client to adjust the start time of a Forecast sequence that has not yet started operation (i.e. where the current Forecast StartTime is in the future). */ + command StartTimeAdjustRequest(StartTimeAdjustRequestRequest): DefaultSuccess = 2; + /** Allows a client to temporarily pause an operation and reduce the ESAs energy demand. */ + command PauseRequest(PauseRequestRequest): DefaultSuccess = 3; + /** Allows a client to cancel the PauseRequest command and enable earlier resumption of operation. */ + command ResumeRequest(): DefaultSuccess = 4; + /** Allows a client to modify a Forecast within the limits allowed by the ESA. */ + command ModifyForecastRequest(ModifyForecastRequestRequest): DefaultSuccess = 5; + /** Allows a client to ask the ESA to recompute its Forecast based on power and time constraints. */ + command RequestConstraintBasedForecast(RequestConstraintBasedForecastRequest): DefaultSuccess = 6; +} + /** Electric Vehicle Supply Equipment (EVSE) is equipment used to charge an Electric Vehicle (EV) or Plug-In Hybrid Electric Vehicle. This cluster provides an interface to the functionality of Electric Vehicle Supply Equipment (EVSE) management. */ provisional cluster EnergyEvse = 153 { revision 2; @@ -1290,6 +1469,34 @@ endpoint 1 { callback attribute clusterRevision; } + server cluster DeviceEnergyManagement { + emits event PowerAdjustStart; + emits event PowerAdjustEnd; + emits event Paused; + emits event Resumed; + callback attribute ESAType; + callback attribute ESACanGenerate; + callback attribute ESAState; + callback attribute absMinPower; + callback attribute absMaxPower; + callback attribute powerAdjustmentCapability; + callback attribute forecast; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + ram attribute clusterRevision default = 2; + + handle command PowerAdjustRequest; + handle command CancelPowerAdjustRequest; + handle command StartTimeAdjustRequest; + handle command PauseRequest; + handle command ResumeRequest; + handle command ModifyForecastRequest; + handle command RequestConstraintBasedForecast; + } + server cluster EnergyEvse { emits event EVConnected; emits event EVNotDetected; @@ -1300,33 +1507,33 @@ endpoint 1 { callback attribute state; callback attribute supplyState; callback attribute faultState; - callback attribute chargingEnabledUntil default = 0; - callback attribute dischargingEnabledUntil default = 0; - callback attribute circuitCapacity default = 0; - callback attribute minimumChargeCurrent default = 6000; - callback attribute maximumChargeCurrent default = 0; - callback attribute maximumDischargeCurrent default = 0; - callback attribute userMaximumChargeCurrent default = 0; - callback attribute randomizationDelayWindow default = 600; + callback attribute chargingEnabledUntil; + callback attribute dischargingEnabledUntil; + callback attribute circuitCapacity; + callback attribute minimumChargeCurrent; + callback attribute maximumChargeCurrent; + callback attribute maximumDischargeCurrent; + callback attribute userMaximumChargeCurrent; + callback attribute randomizationDelayWindow; callback attribute numberOfWeeklyTargets default = 0; callback attribute numberOfDailyTargets default = 1; callback attribute nextChargeStartTime; callback attribute nextChargeTargetTime; callback attribute nextChargeRequiredEnergy; callback attribute nextChargeTargetSoC; - callback attribute approximateEVEfficiency default = 0xFFFF; + callback attribute approximateEVEfficiency; callback attribute stateOfCharge; callback attribute batteryCapacity; callback attribute vehicleID; - callback attribute sessionID default = 0; - callback attribute sessionDuration default = 0; - callback attribute sessionEnergyCharged default = 0; - callback attribute sessionEnergyDischarged default = 0; + callback attribute sessionID; + callback attribute sessionDuration; + callback attribute sessionEnergyCharged; + callback attribute sessionEnergyDischarged; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; callback attribute attributeList; - ram attribute featureMap default = 1; + callback attribute featureMap; ram attribute clusterRevision default = 2; handle command GetTargetsResponse; diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.zap b/examples/energy-management-app/energy-management-common/energy-management-app.zap index 55d62101227dab..8ed4aa6e2167d7 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.zap +++ b/examples/energy-management-app/energy-management-common/energy-management-app.zap @@ -2517,6 +2517,313 @@ } ] }, + { + "name": "Device Energy Management", + "code": 152, + "mfgCode": null, + "define": "DEVICE_ENERGY_MANAGEMENT_CLUSTER", + "side": "server", + "enabled": 1, + "apiMaturity": "provisional", + "commands": [ + { + "name": "PowerAdjustRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CancelPowerAdjustRequest", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "StartTimeAdjustRequest", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "PauseRequest", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ResumeRequest", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ModifyForecastRequest", + "code": 5, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RequestConstraintBasedForecast", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "ESAType", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "ESATypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ESACanGenerate", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ESAState", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "ESAStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AbsMinPower", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "power_mw", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AbsMaxPower", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "power_mw", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PowerAdjustmentCapability", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Forecast", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "ForecastStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "PowerAdjustStart", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "PowerAdjustEnd", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "Paused", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "Resumed", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, { "name": "Energy EVSE", "code": 153, @@ -3063,7 +3370,7 @@ "side": "server", "type": "bitmap32", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 0, "bounded": 0, "defaultValue": "1", diff --git a/examples/energy-management-app/energy-management-common/include/DeviceEnergyManagementDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/DeviceEnergyManagementDelegateImpl.h new file mode 100644 index 00000000000000..464a683510b3d7 --- /dev/null +++ b/examples/energy-management-app/energy-management-common/include/DeviceEnergyManagementDelegateImpl.h @@ -0,0 +1,83 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "app/clusters/device-energy-management-server/device-energy-management-server.h" + +#include +#include +#include + +using chip::Protocols::InteractionModel::Status; +namespace chip { +namespace app { +namespace Clusters { +namespace DeviceEnergyManagement { + +/** + * The application delegate. + */ +class DeviceEnergyManagementDelegate : public DeviceEnergyManagement::Delegate +{ +public: + virtual Status PowerAdjustRequest(const int64_t power, const uint32_t duration) override; + virtual Status CancelPowerAdjustRequest() override; + virtual Status StartTimeAdjustRequest(const uint32_t requestedStartTime) override; + virtual Status PauseRequest(const uint32_t duration) override; + virtual Status ResumeRequest() override; + virtual Status + ModifyForecastRequest(const uint32_t forecastId, + const DataModel::DecodableList & slotAdjustments) override; + virtual Status RequestConstraintBasedForecast( + const DataModel::DecodableList & constraints) override; + + // ------------------------------------------------------------------ + // Get attribute methods + virtual ESATypeEnum GetESAType() override; + virtual bool GetESACanGenerate() override; + virtual ESAStateEnum GetESAState() override; + virtual int64_t GetAbsMinPower() override; + virtual int64_t GetAbsMaxPower() override; + virtual Attributes::PowerAdjustmentCapability::TypeInfo::Type GetPowerAdjustmentCapability() override; + virtual DataModel::Nullable GetForecast() override; + + // ------------------------------------------------------------------ + // Set attribute methods + virtual CHIP_ERROR SetESAType(ESATypeEnum) override; + virtual CHIP_ERROR SetESACanGenerate(bool) override; + virtual CHIP_ERROR SetESAState(ESAStateEnum) override; + virtual CHIP_ERROR SetAbsMinPower(int64_t) override; + virtual CHIP_ERROR SetAbsMaxPower(int64_t) override; + virtual CHIP_ERROR SetPowerAdjustmentCapability(Attributes::PowerAdjustmentCapability::TypeInfo::Type) override; + virtual CHIP_ERROR SetForecast(DataModel::Nullable) override; + +private: + ESATypeEnum mEsaType; + bool mEsaCanGenerate; + ESAStateEnum mEsaState; + int64_t mAbsMinPower; + int64_t mAbsMaxPower; + Attributes::PowerAdjustmentCapability::TypeInfo::Type mPowerAdjustmentCapability; + DataModel::Nullable mForecast; +}; + +} // namespace DeviceEnergyManagement +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/energy-management-app/energy-management-common/include/DeviceEnergyManagementManager.h b/examples/energy-management-app/energy-management-common/include/DeviceEnergyManagementManager.h new file mode 100644 index 00000000000000..6d131dd4ae90fa --- /dev/null +++ b/examples/energy-management-app/energy-management-common/include/DeviceEnergyManagementManager.h @@ -0,0 +1,57 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { + +using namespace chip::app::Clusters::DeviceEnergyManagement; +class DeviceEnergyManagementManager : public Instance +{ +public: + DeviceEnergyManagementManager(EndpointId aEndpointId, DeviceEnergyManagementDelegate & aDelegate, Feature aFeature, + OptionalCommands aOptionalCmds) : + DeviceEnergyManagement::Instance(aEndpointId, aDelegate, aFeature, aOptionalCmds) + { + mDelegate = &aDelegate; + } + + // Delete copy constructor and assignment operator. + DeviceEnergyManagementManager(const DeviceEnergyManagementManager &) = delete; + DeviceEnergyManagementManager(const DeviceEnergyManagementManager &&) = delete; + DeviceEnergyManagementManager & operator=(const DeviceEnergyManagementManager &) = delete; + + CHIP_ERROR Init(); + void Shutdown(); + + DeviceEnergyManagementDelegate * GetDelegate() { return mDelegate; }; + +private: + DeviceEnergyManagementDelegate * mDelegate; +}; + +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h index 5bdac2f8e853d6..7d288809952ee3 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h +++ b/examples/energy-management-app/energy-management-common/include/EVSECallbacks.h @@ -30,26 +30,41 @@ using namespace chip::app::Clusters::EnergyEvse; * This is not specific to the EnergyEVSE cluster, but includes DeviceEnergyManagement * and potential future clusters. */ -enum EVSECallbackType +enum class EVSECallbackType : uint8_t { /* * The State has changed (e.g. from Disabled to Charging, or vice-versa) */ StateChanged, /* - * ChargeCurrent has changed + * ChargeCurrent has changed (e.g. maxChargingCurrent so requires an + update to advertise a different charging current to the EV) */ ChargeCurrentChanged, /* * Charging Preferences have changed + * The daily charging target time, SoC / Added Energy schedules have changed + * and may require the local optimiser to re-run. */ ChargingPreferencesChanged, /* - * DeviceEnergyManagement has changed + * Energy Meter Reading requested from the hardware, e.g. so that the session + * information can be updated. + */ + EnergyMeterReadingRequested, + /* + * The associated DeviceEnergyManagement cluster has changed. This may mean + * that the start time, or power profile or power levels have been adjusted */ DeviceEnergyManagementChanged, }; +enum class ChargingDischargingType : uint8_t +{ + kCharging, + kDischarging +}; + struct EVSECbInfo { EVSECallbackType type; @@ -68,6 +83,13 @@ struct EVSECbInfo { int64_t maximumChargeCurrent; } ChargingCurrent; + + /* for type = EnergyMeterReadingRequested */ + struct + { + ChargingDischargingType meterType; + int64_t * energyMeterValuePtr; + } EnergyMeterReadingRequest; }; }; 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 4ff45e925674aa..df757f7374e672 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h @@ -18,6 +18,7 @@ #pragma once +#include #include using chip::Protocols::InteractionModel::Status; @@ -33,15 +34,26 @@ namespace EnergyEvse { class EVSEManufacturer { public: + EVSEManufacturer(EnergyEvseManager * aInstance) { mInstance = aInstance; } + EnergyEvseManager * GetInstance() { return mInstance; } + EnergyEvseDelegate * GetDelegate() + { + if (mInstance) + { + return mInstance->GetDelegate(); + } + return nullptr; + } + /** * @brief Called at start up to apply hardware settings */ - CHIP_ERROR Init(EnergyEvseManager * aInstance); + CHIP_ERROR Init(); /** * @brief Called at shutdown */ - CHIP_ERROR Shutdown(EnergyEvseManager * aInstance); + CHIP_ERROR Shutdown(); /** * @brief Main Callback handler from delegate to user code @@ -49,8 +61,23 @@ class EVSEManufacturer static void ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg); private: + EnergyEvseManager * mInstance; + + int64_t mLastChargingEnergyMeter = 0; + int64_t mLastDischargingEnergyMeter = 0; }; +/** @brief Helper function to return the singleton EVSEManufacturer instance + * + * This is needed by the EVSEManufacturer class to support TestEventTriggers + * which are called outside of any class context. This allows the EVSEManufacturer + * class to return the relevant Delegate instance in which to invoke the test + * events on. + * + * This function is typically found in main.cpp or wherever the singleton is created. + */ +EVSEManufacturer * GetEvseManufacturer(); + } // namespace EnergyEvse } // namespace Clusters } // namespace app 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 f3c003d081fc6e..d27f35ee63201c 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -31,6 +31,68 @@ namespace app { namespace Clusters { namespace EnergyEvse { +/* Local state machine Events to allow simpler handling of state transitions */ +enum EVSEStateMachineEvent +{ + EVPluggedInEvent, /* EV has been plugged in */ + EVNotDetectedEvent, /* EV has been unplugged or detected as not connected */ + EVNoDemandEvent, /* EV has stopped asking for demand */ + EVDemandEvent, /* EV has asked for demand*/ + ChargingEnabledEvent, /* Charging has been enabled */ + DischargingEnabledEvent, /* Discharging has been enabled */ + DisabledEvent, /* EVSE has been disabled */ + FaultRaised, /* Fault has been raised */ + FaultCleared, /* Fault has been cleared */ +}; + +/** + * Helper class to handle all of the session related info + */ +class EvseSession +{ +public: + EvseSession(EndpointId aEndpoint) { mEndpointId = aEndpoint; } + /** + * @brief This function records the start time and provided energy meter values as part of the new session. + * + * @param chargingMeterValue - The current value of the energy meter (charging) in mWh + * @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh + */ + void StartSession(int64_t chargingMeterValue, int64_t dischargingMeterValue); + + /** + * @brief This function updates the session Duration to allow read attributes to return latest values + */ + void RecalculateSessionDuration(); + + /** + * @brief This function updates the EnergyCharged meter value + * + * @param chargingMeterValue - The value of the energy meter (charging) in mWh + */ + void UpdateEnergyCharged(int64_t chargingMeterValue); + + /** + * @brief This function updates the EnergyDischarged meter value + * + * @param dischargingMeterValue - The value of the energy meter (discharging) in mWh + */ + void UpdateEnergyDischarged(int64_t dischargingMeterValue); + + /* Public members - represent attributes in the cluster */ + DataModel::Nullable mSessionID; + DataModel::Nullable mSessionDuration; + DataModel::Nullable mSessionEnergyCharged; + DataModel::Nullable mSessionEnergyDischarged; + +private: + EndpointId mEndpointId = 0; + + uint32_t mStartTime = 0; // Epoch_s - 0 means it hasn't started yet + int64_t mSessionEnergyChargedAtStart = 0; // in mWh - 0 means it hasn't been set yet + int64_t mSessionEnergyDischargedAtStart = 0; // in mWh - 0 means it hasn't been set yet +}; + /** * The application delegate. */ @@ -74,15 +136,41 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate */ Status HwRegisterEvseCallbackHandler(EVSECallbackFunc handler, intptr_t arg); + /** + * @brief Decides if a timer is needed based on EVSE state and sets a callback if needed + * + * In order to ensure the EVSE restarts charging (if enabled) after power loss + * this should be called after the EVSE is initialised + * (e.g. HwSetMaxHardwareCurrentLimit and HwSetCircuitCapacity have been called) + * and the persisted attributes have been loaded, and time has been synchronised. + * + * If time isn't sync'd yet it will call itself back periodically (if required) + * until time is sync'd. + * + * It is also called when a EnableCharging or EnableDischarging command + * is recv'd to schedule when the EVSE should be automatically disabled based + * on ChargingEnabledUntil / DischargingEnabledUntil expiring. + */ + Status ScheduleCheckOnEnabledTimeout(); + // ----------------------------------------------------------------- // Internal API to allow an EVSE to change its internal state etc Status HwSetMaxHardwareCurrentLimit(int64_t currentmA); + int64_t HwGetMaxHardwareCurrentLimit() { return mMaxHardwareCurrentLimit; } Status HwSetCircuitCapacity(int64_t currentmA); Status HwSetCableAssemblyLimit(int64_t currentmA); + int64_t HwGetCableAssemblyLimit() { return mCableAssemblyCurrentLimit; } Status HwSetState(StateEnum state); + StateEnum HwGetState() { return mHwState; }; Status HwSetFault(FaultStateEnum fault); Status HwSetRFID(ByteSpan uid); Status HwSetVehicleID(const CharSpan & vehID); + Status HwDiagnosticsComplete(); + Status SendEVConnectedEvent(); + Status SendEVNotDetectedEvent(); + Status SendEnergyTransferStartedEvent(); + Status SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum reason); + Status SendFaultEvent(FaultStateEnum newFaultState); // ------------------------------------------------------------------ // Get attribute methods @@ -96,10 +184,10 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate CHIP_ERROR SetFaultState(FaultStateEnum); DataModel::Nullable GetChargingEnabledUntil() override; - CHIP_ERROR SetChargingEnabledUntil(uint32_t); + CHIP_ERROR SetChargingEnabledUntil(DataModel::Nullable); DataModel::Nullable GetDischargingEnabledUntil() override; - CHIP_ERROR SetDischargingEnabledUntil(uint32_t); + CHIP_ERROR SetDischargingEnabledUntil(DataModel::Nullable); int64_t GetCircuitCapacity() override; CHIP_ERROR SetCircuitCapacity(int64_t); @@ -128,7 +216,7 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate DataModel::Nullable GetNextChargeTargetSoC() override; DataModel::Nullable GetApproximateEVEfficiency() override; - CHIP_ERROR SetApproximateEVEfficiency(uint16_t) override; + CHIP_ERROR SetApproximateEVEfficiency(DataModel::Nullable) override; /* SOC attributes */ DataModel::Nullable GetStateOfCharge() override; @@ -143,10 +231,11 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate private: /* Constants */ - static constexpr int DEFAULT_MIN_CHARGE_CURRENT = 6000; /* 6A */ - static constexpr int DEFAULT_USER_MAXIMUM_CHARGE_CURRENT = kMaximumChargeCurrent; /* 80A */ - static constexpr int DEFAULT_RANDOMIZATION_DELAY_WINDOW = 600; /* 600s */ - static constexpr int kMaxVehicleIDBufSize = 32; + 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; /* private variables for controlling the hardware - these are not attributes */ int64_t mMaxHardwareCurrentLimit = 0; /* Hardware current limit in mA */ @@ -155,16 +244,42 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate int64_t mActualChargingCurrentLimit = 0; StateEnum mHwState = StateEnum::kNotPluggedIn; /* Hardware state */ + /* Variables to hold State and SupplyState in case a fault is raised */ + StateEnum mStateBeforeFault = StateEnum::kUnknownEnumValue; + SupplyStateEnum mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue; + /* Callback related */ EVSECallbackWrapper mCallbacks = { .handler = nullptr, .arg = 0 }; /* Wrapper to allow callbacks to be registered */ Status NotifyApplicationCurrentLimitChange(int64_t maximumChargeCurrent); Status NotifyApplicationStateChange(); + Status GetEVSEEnergyMeterValue(ChargingDischargingType meterType, int64_t & aMeterValue); + + /* Local State machine handling */ + Status CheckFaultOrDiagnostic(); + Status HandleStateMachineEvent(EVSEStateMachineEvent event); + Status HandleEVPluggedInEvent(); + Status HandleEVNotDetectedEvent(); + Status HandleEVNoDemandEvent(); + Status HandleEVDemandEvent(); + Status HandleChargingEnabledEvent(); + Status HandleDischargingEnabledEvent(); + Status HandleDisabledEvent(); + Status HandleFaultRaised(); + Status HandleFaultCleared(); /** * @brief Helper function to work out the charge limit based on conditions and settings */ Status ComputeMaxChargeCurrentLimit(); + /** + * @brief This checks if the charging or discharging needs to be disabled + * + * @params pointer to SystemLayer + * @params pointer to EnergyEvseDelegate + */ + static void EvseCheckTimerExpiry(System::Layer * systemLayer, void * delegate); + /* Attributes */ StateEnum mState = StateEnum::kNotPluggedIn; SupplyStateEnum mSupplyState = SupplyStateEnum::kDisabled; @@ -172,11 +287,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 = DEFAULT_MIN_CHARGE_CURRENT; + int64_t mMinimumChargeCurrent = kDefaultMinChargeCurrent; int64_t mMaximumChargeCurrent = 0; int64_t mMaximumDischargeCurrent = 0; - int64_t mUserMaximumChargeCurrent = DEFAULT_USER_MAXIMUM_CHARGE_CURRENT; // TODO update spec - uint32_t mRandomizationDelayWindow = DEFAULT_RANDOMIZATION_DELAY_WINDOW; + int64_t mUserMaximumChargeCurrent = kDefaultUserMaximumChargeCurrent; // TODO update spec + uint32_t mRandomizationDelayWindow = kDefaultRandomizationDelayWindow; /* PREF attributes */ uint8_t mNumberOfWeeklyTargets = 0; uint8_t mNumberOfDailyTargets = 1; @@ -193,11 +308,11 @@ class EnergyEvseDelegate : public EnergyEvse::Delegate /* PNC attributes*/ DataModel::Nullable mVehicleID; - /* Session SESS attributes */ - DataModel::Nullable mSessionID; - DataModel::Nullable mSessionDuration; - DataModel::Nullable mSessionEnergyCharged; - DataModel::Nullable mSessionEnergyDischarged; + /* Session Object */ + EvseSession mSession = EvseSession(mEndpointId); + + /* Helper variable to hold meter val since last EnergyTransferStarted event */ + int64_t mMeterValueAtEnergyTransferStart; }; } // namespace EnergyEvse diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseManager.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseManager.h index 9875c397990ef2..fc0d41b9259643 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseManager.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseManager.h @@ -26,8 +26,8 @@ namespace chip { namespace app { namespace Clusters { +namespace EnergyEvse { -using namespace chip::app::Clusters::EnergyEvse; class EnergyEvseManager : public Instance { public: @@ -46,12 +46,15 @@ class EnergyEvseManager : public Instance CHIP_ERROR Init(); void Shutdown(); + CHIP_ERROR LoadPersistentAttributes(); + EnergyEvseDelegate * GetDelegate() { return mDelegate; }; private: EnergyEvseDelegate * mDelegate; }; +} // namespace EnergyEvse } // namespace Clusters } // namespace app } // namespace chip diff --git a/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp b/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp new file mode 100644 index 00000000000000..4d4d9bf05bbdbb --- /dev/null +++ b/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementDelegateImpl.cpp @@ -0,0 +1,344 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DeviceEnergyManagementDelegateImpl.h" + +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::DeviceEnergyManagement; +using namespace chip::app::Clusters::DeviceEnergyManagement::Attributes; + +using chip::Optional; +using namespace chip::app; +using CostsList = DataModel::List; + +/** + * @brief Delegate handler for PowerAdjustRequest + * + * Note: checking of the validity of the PowerAdjustRequest has been done by the lower layer + * + * This function needs to notify the appliance that it should apply a new power setting. + * It should: + * 1) notify the appliance - if the appliance hardware cannot be adjusted, then return Failure + * 2) start a timer (or restart the existing PowerAdjust timer) for duration seconds + * 3) generate a PowerAdjustStart event (if there is not an existing PowerAdjustRequest running) + * 4) if appropriate, update the forecast with the new expected end time + * + * and when the timer expires: + * 5) notify the appliance's that it can resume its intended power setting (or go idle) + * 6) generate a PowerAdjustEnd event with cause NormalCompletion + * 7) if necessary, update the forecast with new expected end time + */ +Status DeviceEnergyManagementDelegate::PowerAdjustRequest(const int64_t power, const uint32_t duration) +{ + Status status = Status::UnsupportedCommand; // Status::Success; + + // TODO: implement + mEsaState = ESAStateEnum::kPowerAdjustActive; + + // TODO: Generate a PowerAdjustStart Event, then begins to adjust its power + // When done, raise PowerAdjustEnd & ESAState set to kOnline. + + MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, ESAState::Id); + + return status; +} +/** + * @brief Delegate handler for CancelPowerAdjustRequest + * + * Note: checking of the validity of the CancelPowerAdjustRequest has been done by the lower layer + * + * This function needs to notify the appliance that it should resume its intended power setting (or go idle). + + * It should: + * 1) notify the appliance's that it can resume its intended power setting (or go idle) + * 2) generate a PowerAdjustEnd event with cause code Cancelled + * 3) if necessary, update the forecast with new expected end time + */ +Status DeviceEnergyManagementDelegate::CancelPowerAdjustRequest() +{ + Status status = Status::UnsupportedCommand; // Status::Success; + + // TODO: implement + /* TODO: If the command is accepted, the ESA SHALL generate an PowerAdjustEnd Event. */ + mEsaState = ESAStateEnum::kOnline; + MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, ESAState::Id); + + return status; +} + +/** + * @brief Delegate handler for StartTimeAdjustRequest + * + * Note: checking of the validity of the StartTimeAdjustRequest has been done by the lower layer + * + * This function needs to notify the appliance that the forecast has been updated by a client. + * + * It should: + * 1) update the forecast attribute with the revised start time + * 2) send a callback notification to the appliance so it can refresh its internal schedule + */ +Status DeviceEnergyManagementDelegate::StartTimeAdjustRequest(const uint32_t requestedStartTime) +{ + DataModel::Nullable forecast = GetForecast(); + + if (forecast.IsNull()) + { + return Status::Failure; + } + + uint32_t duration = forecast.Value().endTime - forecast.Value().startTime; // the current entire forecast duration + + /* Modify start time and end time */ + forecast.Value().startTime = requestedStartTime; + forecast.Value().endTime = requestedStartTime + duration; + + SetForecast(forecast); // This will increment forecast ID + + // TODO: callback to the appliance to notify it of a new start time + + return Status::Success; +} +/** + * @brief Delegate handler for Pause Request + * + * Note: checking of the validity of the Pause Request has been done by the lower layer + * + * This function needs to notify the appliance that it should now pause. + * It should: + * 1) pause the appliance - if the appliance hardware cannot be paused, then return Failure + * 2) start a timer for duration seconds + * 3) generate a Paused event + * 4) update the forecast with the new expected end time + * + * and when the timer expires: + * 5) restore the appliance's operational state + * 6) generate a Resumed event + * 7) if necessary, update the forecast with new expected end time + */ +Status DeviceEnergyManagementDelegate::PauseRequest(const uint32_t duration) +{ + Status status = Status::UnsupportedCommand; // Status::Success; + // TODO: implement the behaviour above + return status; +} + +/** + * @brief Delegate handler for ResumeRequest + * + * Note: checking of the validity of the ResumeRequest has been done by the lower layer + * + * This function needs to notify the appliance that it should now resume operation + * + * It should: + * 1) restore the appliance's operational state + * 2) generate a Resumed event + * 3) update the forecast with new expected end time (given that the pause duration was shorter than originally requested) + * + */ +Status DeviceEnergyManagementDelegate::ResumeRequest() +{ + Status status = Status::UnsupportedCommand; // Status::Success; + + // TODO: implement the behaviour above + SetESAState(ESAStateEnum::kOnline); + + return status; +} + +/** + * @brief Delegate handler for ModifyForecastRequest + * + * Note: Only basic checking of the validity of the ModifyForecastRequest has been + * done by the lower layer. This is a more complex use-case and requires higher-level + * work by the delegate. + * + * It should: + * 1) determine if the new forecast adjustments are acceptable to the appliance + * - if not return Failure. For example, if it may cause the home to be too hot + * or too cold, or a battery to be insufficiently charged + * 2) if the slot adjustments are acceptable, then update the forecast + * 3) notify the appliance to follow the revised schedule + */ +Status DeviceEnergyManagementDelegate::ModifyForecastRequest( + const uint32_t forecastId, const DataModel::DecodableList & slotAdjustments) +{ + Status status = Status::UnsupportedCommand; // Status::Success; + + // TODO: implement the behaviour above + return status; +} + +/** + * @brief Delegate handler for RequestConstraintBasedForecast + * + * Note: Only basic checking of the validity of the RequestConstraintBasedForecast has been + * done by the lower layer. This is a more complex use-case and requires higher-level + * work by the delegate. + * + * It should: + * 1) perform a higher level optimization (e.g. using tariff information, and user preferences) + * 2) if a solution can be found, then update the forecast, else return Failure + * 3) notify the appliance to follow the revised schedule + */ +Status DeviceEnergyManagementDelegate::RequestConstraintBasedForecast( + const DataModel::DecodableList & constraints) +{ + Status status = Status::UnsupportedCommand; // Status::Success; + // TODO: implement the behaviour above + return status; +} + +// ------------------------------------------------------------------ +// Get attribute methods +ESATypeEnum DeviceEnergyManagementDelegate::GetESAType() +{ + return mEsaType; +} + +bool DeviceEnergyManagementDelegate::GetESACanGenerate() +{ + return mEsaCanGenerate; +} + +ESAStateEnum DeviceEnergyManagementDelegate::GetESAState() +{ + return mEsaState; +} + +int64_t DeviceEnergyManagementDelegate::GetAbsMinPower() +{ + return mAbsMinPower; +} + +int64_t DeviceEnergyManagementDelegate::GetAbsMaxPower() +{ + return mAbsMaxPower; +} + +PowerAdjustmentCapability::TypeInfo::Type DeviceEnergyManagementDelegate::GetPowerAdjustmentCapability() +{ + return mPowerAdjustmentCapability; +} + +DataModel::Nullable DeviceEnergyManagementDelegate::GetForecast() +{ + return mForecast; +} + +// ------------------------------------------------------------------ +// Set attribute methods + +CHIP_ERROR DeviceEnergyManagementDelegate::SetESAType(ESATypeEnum newValue) +{ + ESATypeEnum oldValue = mEsaType; + + if (newValue >= ESATypeEnum::kUnknownEnumValue) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + + mEsaType = newValue; + if (oldValue != newValue) + { + ChipLogDetail(AppServer, "mEsaType updated to %d", static_cast(mEsaType)); + MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, ESAType::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceEnergyManagementDelegate::SetESACanGenerate(bool newValue) +{ + bool oldValue = mEsaCanGenerate; + + mEsaCanGenerate = newValue; + if (oldValue != newValue) + { + ChipLogDetail(AppServer, "mEsaCanGenerate updated to %d", static_cast(mEsaCanGenerate)); + MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, ESACanGenerate::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceEnergyManagementDelegate::SetESAState(ESAStateEnum newValue) +{ + ESAStateEnum oldValue = mEsaState; + + if (newValue >= ESAStateEnum::kUnknownEnumValue) + { + return CHIP_IM_GLOBAL_STATUS(ConstraintError); + } + + mEsaState = newValue; + if (oldValue != newValue) + { + ChipLogDetail(AppServer, "mEsaState updated to %d", static_cast(mEsaState)); + MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, ESAState::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceEnergyManagementDelegate::SetAbsMinPower(int64_t newValue) +{ + int64_t oldValue = mAbsMinPower; + + mAbsMinPower = newValue; + if (oldValue != newValue) + { + ChipLogDetail(AppServer, "mAbsMinPower updated to %d", static_cast(mAbsMinPower)); + MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, AbsMinPower::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceEnergyManagementDelegate::SetAbsMaxPower(int64_t newValue) +{ + int64_t oldValue = mAbsMaxPower; + + mAbsMaxPower = newValue; + if (oldValue != newValue) + { + ChipLogDetail(AppServer, "mAbsMaxPower updated to %d", static_cast(mAbsMaxPower)); + MatterReportingAttributeChangeCallback(mEndpointId, DeviceEnergyManagement::Id, AbsMaxPower::Id); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR +DeviceEnergyManagementDelegate::SetPowerAdjustmentCapability(PowerAdjustmentCapability::TypeInfo::Type powerAdjustmentCapability) +{ + // TODO see Issue #31147 + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceEnergyManagementDelegate::SetForecast(DataModel::Nullable forecast) +{ + // TODO see Issue #31147 + + return CHIP_NO_ERROR; +} diff --git a/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp b/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp new file mode 100644 index 00000000000000..c31e0624e4c743 --- /dev/null +++ b/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp @@ -0,0 +1,33 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::DeviceEnergyManagement; + +CHIP_ERROR DeviceEnergyManagementManager::Init() +{ + return Instance::Init(); +} + +void DeviceEnergyManagementManager::Shutdown() +{ + Instance::Shutdown(); +} 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 8daf1781103831..fe51c9da664b6d 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -18,53 +18,74 @@ #include #include +#include +using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; -CHIP_ERROR EVSEManufacturer::Init(EnergyEvseManager * aInstance) +CHIP_ERROR EVSEManufacturer::Init() { /* Manufacturers should modify this to do any custom initialisation */ /* Register callbacks */ - EnergyEvseDelegate * dg = aInstance->GetDelegate(); + EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); if (dg == nullptr) { - ChipLogError(AppServer, "Delegate is not initialized"); + ChipLogError(AppServer, "EVSE Delegate is not initialized"); return CHIP_ERROR_UNINITIALIZED; } - dg->HwRegisterEvseCallbackHandler(ApplicationCallbackHandler, reinterpret_cast(nullptr)); + dg->HwRegisterEvseCallbackHandler(ApplicationCallbackHandler, reinterpret_cast(this)); - /* Set the EVSE Hardware Maximum current limit */ - // For Manufacturer to specify the hardware capability in mA - dg->HwSetMaxHardwareCurrentLimit(32000); - - // For Manufacturer to specify the CircuitCapacity (e.g. from DIP switches) - dg->HwSetCircuitCapacity(20000); - - /* For now let's pretend the EV is plugged in, and asking for demand */ - dg->HwSetState(StateEnum::kPluggedInDemand); - dg->HwSetCableAssemblyLimit(63000); - - /* For now let's pretend the vehicle ID is set */ - dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_123456789")); - dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_9876543210")); + /* + * This is an example implementation for manufacturers to consider + * + * For Manufacturer to specify the hardware capability in mA: + * dg->HwSetMaxHardwareCurrentLimit(32000); // 32A + * + * For Manufacturer to specify the CircuitCapacity in mA (e.g. from DIP switches) + * dg->HwSetCircuitCapacity(20000); // 20A + * + */ - /* This next one will fail because it is too long */ - dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_9876543210TOOOOOOOOOOOOOOOOOOO")); - - /* For now let's pretend the RFID sensor was triggered - send an event */ - uint8_t uid[10] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; - dg->HwSetRFID(ByteSpan(uid)); + /* Once the system is initialised then check to see if the state was restored + * (e.g. after a power outage), and if the Enable timer check needs to be started + */ + dg->ScheduleCheckOnEnabledTimeout(); return CHIP_NO_ERROR; } -CHIP_ERROR EVSEManufacturer::Shutdown(EnergyEvseManager * aInstance) -{ +/* + * When the EV is plugged in, and asking for demand change the state + * and set the CableAssembly current limit + * + * EnergyEvseDelegate * dg = GetEvseManufacturer()->GetDelegate(); + * if (dg == nullptr) + * { + * ChipLogError(AppServer, "Delegate is not initialized"); + * return CHIP_ERROR_UNINITIALIZED; + * } + * + * dg->HwSetState(StateEnum::kPluggedInDemand); + * dg->HwSetCableAssemblyLimit(63000); // 63A = 63000mA + * + * + * If the vehicle ID can be retrieved (e.g. over Powerline) + * dg->HwSetVehicleID(CharSpan::fromCharString("TEST_VEHICLE_123456789")); + * + * + * If the EVSE has an RFID sensor, the RFID value read can cause an event to be sent + * (e.g. can be used to indicate if a user as tried to activate the charging) + * + * uint8_t uid[10] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; + * dg->HwSetRFID(ByteSpan(uid)); + */ +CHIP_ERROR EVSEManufacturer::Shutdown() +{ return CHIP_NO_ERROR; } @@ -78,6 +99,8 @@ CHIP_ERROR EVSEManufacturer::Shutdown(EnergyEvseManager * aInstance) */ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg) { + EVSEManufacturer * pClass = reinterpret_cast(arg); + switch (cb->type) { case EVSECallbackType::StateChanged: @@ -87,7 +110,177 @@ void EVSEManufacturer::ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_ ChipLogProgress(AppServer, "EVSE callback - maxChargeCurrent changed to %ld", static_cast(cb->ChargingCurrent.maximumChargeCurrent)); break; + case EVSECallbackType::EnergyMeterReadingRequested: + ChipLogProgress(AppServer, "EVSE callback - EnergyMeterReadingRequested"); + if (cb->EnergyMeterReadingRequest.meterType == ChargingDischargingType::kCharging) + { + *(cb->EnergyMeterReadingRequest.energyMeterValuePtr) = pClass->mLastChargingEnergyMeter; + } + else + { + *(cb->EnergyMeterReadingRequest.energyMeterValuePtr) = pClass->mLastDischargingEnergyMeter; + } + break; + + default: + ChipLogError(AppServer, "Unhandled EVSE Callback type %d", static_cast(cb->type)); + } +} + +struct EVSETestEventSaveData +{ + int64_t mOldMaxHardwareCurrentLimit; + int64_t mOldCircuitCapacity; + int64_t mOldUserMaximumChargeCurrent; + int64_t mOldCableAssemblyLimit; + StateEnum mOldHwStateBasic; /* For storing hwState before Basic Func event */ + StateEnum mOldHwStatePluggedIn; /* For storing hwState before PluggedIn event */ + StateEnum mOldHwStatePluggedInDemand; /* For storing hwState before PluggedInDemand event */ +}; + +static EVSETestEventSaveData sEVSETestEventSaveData; + +EnergyEvseDelegate * GetEvseDelegate() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); + EnergyEvseDelegate * dg = mn->GetDelegate(); + VerifyOrDieWithMsg(dg != nullptr, AppServer, "EVSE Delegate is null"); + + return dg; +} + +void SetTestEventTrigger_BasicFunctionality() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + sEVSETestEventSaveData.mOldMaxHardwareCurrentLimit = dg->HwGetMaxHardwareCurrentLimit(); + sEVSETestEventSaveData.mOldCircuitCapacity = dg->GetCircuitCapacity(); + sEVSETestEventSaveData.mOldUserMaximumChargeCurrent = dg->GetUserMaximumChargeCurrent(); + sEVSETestEventSaveData.mOldHwStateBasic = dg->HwGetState(); + + dg->HwSetMaxHardwareCurrentLimit(32000); + dg->HwSetCircuitCapacity(32000); + dg->SetUserMaximumChargeCurrent(32000); + dg->HwSetState(StateEnum::kNotPluggedIn); +} +void SetTestEventTrigger_BasicFunctionalityClear() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetMaxHardwareCurrentLimit(sEVSETestEventSaveData.mOldMaxHardwareCurrentLimit); + dg->HwSetCircuitCapacity(sEVSETestEventSaveData.mOldCircuitCapacity); + dg->SetUserMaximumChargeCurrent(sEVSETestEventSaveData.mOldUserMaximumChargeCurrent); + dg->HwSetState(sEVSETestEventSaveData.mOldHwStateBasic); +} +void SetTestEventTrigger_EVPluggedIn() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + sEVSETestEventSaveData.mOldCableAssemblyLimit = dg->HwGetCableAssemblyLimit(); + sEVSETestEventSaveData.mOldHwStatePluggedIn = dg->HwGetState(); + + dg->HwSetCableAssemblyLimit(63000); + dg->HwSetState(StateEnum::kPluggedInNoDemand); +} +void SetTestEventTrigger_EVPluggedInClear() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + dg->HwSetCableAssemblyLimit(sEVSETestEventSaveData.mOldCableAssemblyLimit); + dg->HwSetState(sEVSETestEventSaveData.mOldHwStatePluggedIn); +} + +void SetTestEventTrigger_EVChargeDemand() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + sEVSETestEventSaveData.mOldHwStatePluggedInDemand = dg->HwGetState(); + dg->HwSetState(StateEnum::kPluggedInDemand); +} +void SetTestEventTrigger_EVChargeDemandClear() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetState(sEVSETestEventSaveData.mOldHwStatePluggedInDemand); +} +void SetTestEventTrigger_EVSEGroundFault() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetFault(FaultStateEnum::kGroundFault); +} + +void SetTestEventTrigger_EVSEOverTemperatureFault() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetFault(FaultStateEnum::kOverTemperature); +} + +void SetTestEventTrigger_EVSEFaultClear() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwSetFault(FaultStateEnum::kNoError); +} + +void SetTestEventTrigger_EVSEDiagnosticsComplete() +{ + EnergyEvseDelegate * dg = GetEvseDelegate(); + + dg->HwDiagnosticsComplete(); +} + +bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) +{ + EnergyEvseTrigger trigger = static_cast(eventTrigger); + + switch (trigger) + { + case EnergyEvseTrigger::kBasicFunctionality: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => Basic Functionality install"); + SetTestEventTrigger_BasicFunctionality(); + break; + case EnergyEvseTrigger::kBasicFunctionalityClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => Basic Functionality clear"); + SetTestEventTrigger_BasicFunctionalityClear(); + break; + case EnergyEvseTrigger::kEVPluggedIn: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV plugged in"); + SetTestEventTrigger_EVPluggedIn(); + break; + case EnergyEvseTrigger::kEVPluggedInClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV unplugged"); + SetTestEventTrigger_EVPluggedInClear(); + break; + case EnergyEvseTrigger::kEVChargeDemand: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV Charge Demand"); + SetTestEventTrigger_EVChargeDemand(); + break; + case EnergyEvseTrigger::kEVChargeDemandClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EV Charge NoDemand"); + SetTestEventTrigger_EVChargeDemandClear(); + break; + case EnergyEvseTrigger::kEVSEGroundFault: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE has a GroundFault fault"); + SetTestEventTrigger_EVSEGroundFault(); + break; + case EnergyEvseTrigger::kEVSEOverTemperatureFault: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE has a OverTemperature fault"); + SetTestEventTrigger_EVSEOverTemperatureFault(); + break; + case EnergyEvseTrigger::kEVSEFaultClear: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE faults have cleared"); + SetTestEventTrigger_EVSEFaultClear(); + break; + case EnergyEvseTrigger::kEVSEDiagnosticsComplete: + ChipLogProgress(Support, "[EnergyEVSE-Test-Event] => EVSE Diagnostics Completed"); + SetTestEventTrigger_EVSEDiagnosticsComplete(); + break; + default: - ChipLogError(AppServer, "Unhandler EVSE Callback type %d", static_cast(cb->type)); + return false; } + + return true; } 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 4cc83eaaf8a835..2b4a6e44f5fcea 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp @@ -20,6 +20,7 @@ #include #include #include +#include using namespace chip; using namespace chip::app; @@ -41,6 +42,13 @@ 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 */ @@ -48,33 +56,10 @@ Status EnergyEvseDelegate::Disable() { ChipLogProgress(AppServer, "EnergyEvseDelegate::Disable()"); - /* Update State */ - switch (mHwState) - { - case StateEnum::kNotPluggedIn: - SetState(StateEnum::kNotPluggedIn); - break; - - case StateEnum::kPluggedInNoDemand: - SetState(StateEnum::kPluggedInNoDemand); - break; - - case StateEnum::kPluggedInDemand: - SetState(StateEnum::kPluggedInDemand); - break; - - default: - ChipLogError(AppServer, "Unexpected EVSE hardware state"); - SetState(StateEnum::kFault); - break; - } - - /* update SupplyState */ - SetSupplyState(SupplyStateEnum::kDisabled); - + DataModel::Nullable disableTime(0); /* update ChargingEnabledUntil & DischargingEnabledUntil to show 0 */ - SetChargingEnabledUntil(0); - SetDischargingEnabledUntil(0); + SetChargingEnabledUntil(disableTime); + SetDischargingEnabledUntil(disableTime); /* update MinimumChargeCurrent & MaximumChargeCurrent to 0 */ SetMinimumChargeCurrent(0); @@ -83,10 +68,7 @@ Status EnergyEvseDelegate::Disable() /* update MaximumDischargeCurrent to 0 */ SetMaximumDischargeCurrent(0); - NotifyApplicationStateChange(); - // TODO: Generate events - - return Status::Success; + return HandleStateMachineEvent(EVSEStateMachineEvent::DisabledEvent); } /** @@ -123,95 +105,131 @@ Status EnergyEvseDelegate::EnableCharging(const DataModel::Nullable & { /* Charging enabled indefinitely */ ChipLogError(AppServer, "Charging enabled indefinitely"); + SetChargingEnabledUntil(chargingEnabledUntil); } else { /* check chargingEnabledUntil is in the future */ ChipLogError(AppServer, "Charging enabled until: %lu", static_cast(chargingEnabledUntil.Value())); - // TODO - // if (checkChargingEnabled) + SetChargingEnabledUntil(chargingEnabledUntil); } - /* Check current state isn't already enabled */ - - /* If charging is already enabled, check that the parameters may have - changed, these may override an existing charging command */ - switch (mHwState) - { - case StateEnum::kNotPluggedIn: - // TODO handle errors here - SetState(StateEnum::kNotPluggedIn); - break; - - case StateEnum::kPluggedInNoDemand: - // TODO handle errors here - // TODO REFACTOR per Andrei's comment in PR30857 - can we collapse this switch statement? - SetState(StateEnum::kPluggedInNoDemand); - break; - - case StateEnum::kPluggedInDemand: - /* If the EVSE is asking for demand then enable charging */ - SetState(StateEnum::kPluggedInCharging); - break; - - default: - ChipLogError(AppServer, "Unexpected EVSE hardware state"); - SetState(StateEnum::kFault); - break; - } - - /* update SupplyState to say that charging is now enabled */ - SetSupplyState(SupplyStateEnum::kChargingEnabled); - /* If it looks ok, store the min & max charging current */ mMaximumChargingCurrentLimitFromCommand = maximumChargeCurrent; SetMinimumChargeCurrent(minimumChargeCurrent); // TODO persist these to KVS - // TODO: Generate events + ComputeMaxChargeCurrentLimit(); - NotifyApplicationStateChange(); - - return this->ComputeMaxChargeCurrentLimit(); + return HandleStateMachineEvent(EVSEStateMachineEvent::ChargingEnabledEvent); } /** * @brief Called when EVSE cluster receives EnableDischarging command * * @param dischargingEnabledUntil (can be null to indefinite discharging) - * @param maximumChargeCurrent (in mA) + * @param maximumDischargeCurrent (in mA) */ Status EnergyEvseDelegate::EnableDischarging(const DataModel::Nullable & dischargingEnabledUntil, const int64_t & maximumDischargeCurrent) { ChipLogProgress(AppServer, "EnergyEvseDelegate::EnableDischarging() called."); - /* update SupplyState */ - SetSupplyState(SupplyStateEnum::kDischargingEnabled); + // TODO save the maxDischarging Current + // TODO Do something with timestamp - // TODO: Generate events + return HandleStateMachineEvent(EVSEStateMachineEvent::DischargingEnabledEvent); +} - NotifyApplicationStateChange(); +/** + * @brief Routine to help schedule a timer callback to check if the EVSE should go disabled + * + * If the clock is sync'd we can work out when to call back to check when to disable the EVSE + * automatically. If the clock isn't sync'd the we just set a timer to check once every 30s. + * + * We first check the SupplyState to check if it is EnabledCharging or EnabledDischarging + * Then if the EnabledCharging/DischargingUntil is not Null, then we compute a delay to come + * back and check. + */ +Status EnergyEvseDelegate::ScheduleCheckOnEnabledTimeout() +{ + + uint32_t chipEpoch = 0; + DataModel::Nullable enabledUntilTime; + if (mSupplyState == SupplyStateEnum::kChargingEnabled) + { + enabledUntilTime = GetChargingEnabledUntil(); + } + else if (mSupplyState == SupplyStateEnum::kDischargingEnabled) + { + enabledUntilTime = GetDischargingEnabledUntil(); + } + else + { + // In all other states the EVSE is disabled + return Status::Success; + } + + if (enabledUntilTime.IsNull()) + { + /* This is enabled indefinitely so don't schedule a callback */ + return Status::Success; + } + + CHIP_ERROR err = GetEpochTS(chipEpoch); + if (err == CHIP_NO_ERROR) + { + /* time is sync'd */ + int32_t delta = static_cast(enabledUntilTime.Value() - chipEpoch); + if (delta > 0) + { + /* The timer hasn't expired yet - set a timer to check in the future */ + ChipLogDetail(AppServer, "Setting EVSE Enable check timer for %ld seconds", static_cast(delta)); + DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(delta), EvseCheckTimerExpiry, this); + } + else + { + /* we have gone past the enabledUntilTime - so we need to disable */ + ChipLogDetail(AppServer, "EVSE enable time expired, disabling charging"); + Disable(); + } + } + 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), + EvseCheckTimerExpiry, this); + } return Status::Success; } +void EnergyEvseDelegate::EvseCheckTimerExpiry(System::Layer * systemLayer, void * delegate) +{ + EnergyEvseDelegate * dg = reinterpret_cast(delegate); + + dg->ScheduleCheckOnEnabledTimeout(); +} + /** * @brief Called when EVSE cluster receives StartDiagnostics command + * + * NOTE: Application code needs to call HwDiagnosticsComplete + * once diagnostics have been completed. */ Status EnergyEvseDelegate::StartDiagnostics() { /* For EVSE manufacturers to customize */ ChipLogProgress(AppServer, "EnergyEvseDelegate::StartDiagnostics()"); - /* update SupplyState to indicate we are now in Diagnostics mode */ - SetSupplyState(SupplyStateEnum::kDisabledDiagnostics); - - // TODO: Generate events - - // TODO: Notify Application to implement Diagnostics + if (mSupplyState != SupplyStateEnum::kDisabled) + { + ChipLogError(AppServer, "EVSE: cannot be put into diagnostics mode if it is not Disabled!"); + return Status::Failure; + } - NotifyApplicationStateChange(); + // Update the SupplyState - this will automatically callback the Application StateChanged callback + SetSupplyState(SupplyStateEnum::kDisabledDiagnostics); return Status::Success; } @@ -259,7 +277,7 @@ Status EnergyEvseDelegate::HwSetMaxHardwareCurrentLimit(int64_t currentmA) /* there is no attribute to store this so store in private variable */ mMaxHardwareCurrentLimit = currentmA; - return this->ComputeMaxChargeCurrentLimit(); + return ComputeMaxChargeCurrentLimit(); } /** @@ -281,7 +299,7 @@ Status EnergyEvseDelegate::HwSetCircuitCapacity(int64_t currentmA) mCircuitCapacity = currentmA; MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, CircuitCapacity::Id); - return this->ComputeMaxChargeCurrentLimit(); + return ComputeMaxChargeCurrentLimit(); } /** @@ -306,40 +324,99 @@ Status EnergyEvseDelegate::HwSetCableAssemblyLimit(int64_t currentmA) /* there is no attribute to store this so store in private variable */ mCableAssemblyCurrentLimit = currentmA; - return this->ComputeMaxChargeCurrentLimit(); + return ComputeMaxChargeCurrentLimit(); } /** * @brief Called by EVSE Hardware to indicate if EV is detected * - * The only allowed states that the EVSE hardware can set are: + * The only allowed states that the EVSE hardware can tell us about are: * kNotPluggedIn * kPluggedInNoDemand * kPluggedInDemand * + * The actual overall state is more complex and includes faults, + * enable & disable charging or discharging etc. + * * @param StateEnum - the state of the EV being plugged in and asking for demand etc */ -Status EnergyEvseDelegate::HwSetState(StateEnum state) +Status EnergyEvseDelegate::HwSetState(StateEnum newState) { - switch (state) + switch (newState) { case StateEnum::kNotPluggedIn: - // TODO - work out logic here - mHwState = state; + switch (mHwState) + { + case StateEnum::kNotPluggedIn: + // No change + break; + case StateEnum::kPluggedInNoDemand: + case StateEnum::kPluggedInDemand: + /* EVSE has been unplugged now */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVNotDetectedEvent); + break; + + default: + // invalid value for mHwState + ChipLogError(AppServer, "HwSetState newstate(kNotPluggedIn) - Invalid value for mHwState"); + mHwState = newState; // set it to the new state anyway + break; + } break; + case StateEnum::kPluggedInNoDemand: - // TODO - work out logic here - mHwState = state; + switch (mHwState) + { + case StateEnum::kNotPluggedIn: + /* EV was unplugged, now is plugged in */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVPluggedInEvent); + break; + case StateEnum::kPluggedInNoDemand: + // No change + break; + case StateEnum::kPluggedInDemand: + /* EV was plugged in and wanted demand, now doesn't want demand */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVNoDemandEvent); + break; + default: + // invalid value for mHwState + ChipLogError(AppServer, "HwSetState newstate(kPluggedInNoDemand) - Invalid value for mHwState"); + mHwState = newState; // set it to the new state anyway + break; + } break; case StateEnum::kPluggedInDemand: - // TODO - work out logic here - mHwState = state; + switch (mHwState) + { + case StateEnum::kNotPluggedIn: + /* EV was unplugged, now is plugged in and wants demand */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVPluggedInEvent); + HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent); + break; + case StateEnum::kPluggedInNoDemand: + /* EV was plugged in and didn't want demand, now does want demand */ + mHwState = newState; + HandleStateMachineEvent(EVSEStateMachineEvent::EVDemandEvent); + break; + case StateEnum::kPluggedInDemand: + // No change + break; + default: + // invalid value for mHwState + ChipLogError(AppServer, "HwSetState newstate(kPluggedInDemand) - Invalid value for mHwState"); + mHwState = newState; // set it to the new state anyway + break; + } break; default: /* All other states should be managed by the Delegate */ - // TODO (assert?) - break; + ChipLogError(AppServer, "HwSetState received invalid enum from caller"); + return Status::Failure; } return Status::Success; @@ -350,29 +427,35 @@ Status EnergyEvseDelegate::HwSetState(StateEnum state) * * @param FaultStateEnum - the fault condition detected */ -Status EnergyEvseDelegate::HwSetFault(FaultStateEnum fault) +Status EnergyEvseDelegate::HwSetFault(FaultStateEnum newFaultState) { ChipLogProgress(AppServer, "EnergyEvseDelegate::Fault()"); - if (fault == FaultStateEnum::kNoError) + if (mFaultState == newFaultState) { - /* Update State to previous state */ - // TODO: need to work out the logic here! + ChipLogError(AppServer, "No change in fault state, ignoring call"); + return Status::Failure; + } + + /** Before we do anything we log the fault + * any change in FaultState reports previous fault and new fault + * and the state prior to the fault being raised */ + SendFaultEvent(newFaultState); + + /* Updated FaultState before we call into the handlers */ + SetFaultState(newFaultState); - /* Update SupplyState to previous state */ + if (newFaultState == FaultStateEnum::kNoError) + { + /* Fault has been cleared */ + HandleStateMachineEvent(EVSEStateMachineEvent::FaultCleared); } else { - /* Update State & SupplyState */ - SetState(StateEnum::kFault); - SetSupplyState(SupplyStateEnum::kDisabledError); + /* a new Fault has been raised */ + HandleStateMachineEvent(EVSEStateMachineEvent::FaultRaised); } - /* Update FaultState */ - SetFaultState(fault); - - // TODO: Generate events - return Status::Success; } @@ -436,10 +519,329 @@ Status EnergyEvseDelegate::HwSetVehicleID(const CharSpan & newValue) return Status::Success; } +/** + * @brief Called by EVSE Hardware to indicate that it has finished its diagnostics test + */ +Status EnergyEvseDelegate::HwDiagnosticsComplete() +{ + if (mSupplyState != SupplyStateEnum::kDisabledDiagnostics) + { + ChipLogError(AppServer, "Incorrect state to be completing diagnostics"); + return Status::Failure; + } + + /* Restore the SupplyState to Disabled (per spec) - client will need to + * re-enable charging or discharging to get out of this state */ + SetSupplyState(SupplyStateEnum::kDisabled); + + return Status::Success; +} /* --------------------------------------------------------------------------- * Functions below are private helper functions internal to the delegate */ +/** + * @brief Main EVSE state machine + * + * This routine handles state transition events to determine behaviour + * + * + */ +Status EnergyEvseDelegate::HandleStateMachineEvent(EVSEStateMachineEvent event) +{ + switch (event) + { + case EVSEStateMachineEvent::EVPluggedInEvent: + ChipLogDetail(AppServer, "EVSE: EV PluggedIn event"); + return HandleEVPluggedInEvent(); + break; + case EVSEStateMachineEvent::EVNotDetectedEvent: + ChipLogDetail(AppServer, "EVSE: EV NotDetected event"); + return HandleEVNotDetectedEvent(); + break; + case EVSEStateMachineEvent::EVNoDemandEvent: + ChipLogDetail(AppServer, "EVSE: EV NoDemand event"); + return HandleEVNoDemandEvent(); + break; + case EVSEStateMachineEvent::EVDemandEvent: + ChipLogDetail(AppServer, "EVSE: EV Demand event"); + return HandleEVDemandEvent(); + break; + case EVSEStateMachineEvent::ChargingEnabledEvent: + ChipLogDetail(AppServer, "EVSE: ChargingEnabled event"); + return HandleChargingEnabledEvent(); + break; + case EVSEStateMachineEvent::DischargingEnabledEvent: + ChipLogDetail(AppServer, "EVSE: DischargingEnabled event"); + return HandleDischargingEnabledEvent(); + break; + case EVSEStateMachineEvent::DisabledEvent: + ChipLogDetail(AppServer, "EVSE: Disabled event"); + return HandleDisabledEvent(); + break; + case EVSEStateMachineEvent::FaultRaised: + ChipLogDetail(AppServer, "EVSE: FaultRaised event"); + return HandleFaultRaised(); + break; + case EVSEStateMachineEvent::FaultCleared: + ChipLogDetail(AppServer, "EVSE: FaultCleared event"); + return HandleFaultCleared(); + break; + default: + return Status::Failure; + } + return Status::Success; +} + +Status EnergyEvseDelegate::HandleEVPluggedInEvent() +{ + /* check if we are already plugged in or not */ + if (mState == StateEnum::kNotPluggedIn) + { + /* EV was not plugged in - start a new session */ + // TODO get energy meter readings + mSession.StartSession(0, 0); + SendEVConnectedEvent(); + + /* Set the state to either PluggedInNoDemand or PluggedInDemand as indicated by mHwState */ + SetState(mHwState); + } + // else we are already plugged in - ignore + return Status::Success; +} + +Status EnergyEvseDelegate::HandleEVNotDetectedEvent() +{ + if (mState == StateEnum::kPluggedInCharging || mState == StateEnum::kPluggedInDischarging) + { + /* + * EV was transferring current - unusual to get to this case without + * first having the state set to kPluggedInNoDemand or kPluggedInDemand + */ + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kOther); + } + + SendEVNotDetectedEvent(); + SetState(StateEnum::kNotPluggedIn); + return Status::Success; +} + +Status EnergyEvseDelegate::HandleEVNoDemandEvent() +{ + if (mState == StateEnum::kPluggedInCharging || mState == StateEnum::kPluggedInDischarging) + { + /* + * EV was transferring current - EV decided to stop + */ + mSession.RecalculateSessionDuration(); + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVStopped); + } + /* We must still be plugged in to get here - so no need to check if we are plugged in! */ + SetState(StateEnum::kPluggedInNoDemand); + return Status::Success; +} +Status EnergyEvseDelegate::HandleEVDemandEvent() +{ + /* Check to see if the supply is enabled for charging / discharging*/ + switch (mSupplyState) + { + case SupplyStateEnum::kChargingEnabled: + ComputeMaxChargeCurrentLimit(); + SetState(StateEnum::kPluggedInCharging); + SendEnergyTransferStartedEvent(); + break; + case SupplyStateEnum::kDischargingEnabled: + // TODO ComputeMaxDischargeCurrentLimit() - Needs to be implemented + SetState(StateEnum::kPluggedInDischarging); + SendEnergyTransferStartedEvent(); + break; + case SupplyStateEnum::kDisabled: + case SupplyStateEnum::kDisabledError: + case SupplyStateEnum::kDisabledDiagnostics: + /* We must be plugged in, and the event is asking for demand + * but we can't charge or discharge now - leave it as kPluggedInDemand */ + SetState(StateEnum::kPluggedInDemand); + break; + default: + break; + } + return Status::Success; +} + +Status EnergyEvseDelegate::CheckFaultOrDiagnostic() +{ + if (mFaultState != FaultStateEnum::kNoError) + { + ChipLogError(AppServer, "EVSE: Trying to handle command when fault is present"); + return Status::Failure; + } + + if (mSupplyState == SupplyStateEnum::kDisabledDiagnostics) + { + ChipLogError(AppServer, "EVSE: Trying to handle command when in diagnostics mode"); + return Status::Failure; + } + return Status::Success; +} + +Status EnergyEvseDelegate::HandleChargingEnabledEvent() +{ + /* Check there is no Fault or Diagnostics condition */ + Status status = CheckFaultOrDiagnostic(); + if (status != Status::Success) + { + return status; + } + + /* update SupplyState to say that charging is now enabled */ + SetSupplyState(SupplyStateEnum::kChargingEnabled); + + switch (mState) + { + case StateEnum::kNotPluggedIn: + case StateEnum::kPluggedInNoDemand: + break; + case StateEnum::kPluggedInDemand: + ComputeMaxChargeCurrentLimit(); + SetState(StateEnum::kPluggedInCharging); + SendEnergyTransferStartedEvent(); + break; + case StateEnum::kPluggedInCharging: + break; + case StateEnum::kPluggedInDischarging: + /* Switched from discharging to charging */ + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped); + + ComputeMaxChargeCurrentLimit(); + SetState(StateEnum::kPluggedInCharging); + SendEnergyTransferStartedEvent(); + break; + default: + break; + } + + ScheduleCheckOnEnabledTimeout(); + + return Status::Success; +} +Status EnergyEvseDelegate::HandleDischargingEnabledEvent() +{ + /* Check there is no Fault or Diagnostics condition */ + Status status = CheckFaultOrDiagnostic(); + if (status != Status::Success) + { + return status; + } + /* update SupplyState to say that charging is now enabled */ + SetSupplyState(SupplyStateEnum::kDischargingEnabled); + + switch (mState) + { + case StateEnum::kNotPluggedIn: + case StateEnum::kPluggedInNoDemand: + break; + case StateEnum::kPluggedInDemand: + // TODO call ComputeMaxDischargeCurrentLimit() + SetState(StateEnum::kPluggedInDischarging); + SendEnergyTransferStartedEvent(); + break; + case StateEnum::kPluggedInCharging: + /* Switched from charging to discharging */ + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped); + + // TODO call ComputeMaxDischargeCurrentLimit() + SetState(StateEnum::kPluggedInDischarging); + SendEnergyTransferStartedEvent(); + break; + case StateEnum::kPluggedInDischarging: + default: + break; + } + + ScheduleCheckOnEnabledTimeout(); + + return Status::Success; +} +Status EnergyEvseDelegate::HandleDisabledEvent() +{ + /* Check there is no Fault or Diagnostics condition */ + Status status = CheckFaultOrDiagnostic(); + if (status != Status::Success) + { + return status; + } + + /* update SupplyState to say that charging is now enabled */ + SetSupplyState(SupplyStateEnum::kDisabled); + + switch (mState) + { + case StateEnum::kNotPluggedIn: + case StateEnum::kPluggedInNoDemand: + case StateEnum::kPluggedInDemand: + break; + case StateEnum::kPluggedInCharging: + case StateEnum::kPluggedInDischarging: + SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum::kEVSEStopped); + SetState(mHwState); + break; + default: + break; + } + + return Status::Success; +} + +/** + * @brief This handles the new fault + * + * Note that if multiple faults happen and this is called repeatedly + * We only save the previous State and SupplyState if its the first raising + * of the fault, so we can restore the state back once all faults have cleared +)*/ +Status EnergyEvseDelegate::HandleFaultRaised() +{ + /* Save the current State and SupplyState so we can restore them if the fault clears */ + if (mStateBeforeFault == StateEnum::kUnknownEnumValue) + { + /* No existing fault - save this value to restore it later if it clears */ + mStateBeforeFault = mState; + } + + if (mSupplyStateBeforeFault == SupplyStateEnum::kUnknownEnumValue) + { + /* No existing fault */ + mSupplyStateBeforeFault = mSupplyState; + } + + /* Update State & SupplyState */ + SetState(StateEnum::kFault); + SetSupplyState(SupplyStateEnum::kDisabledError); + + return Status::Success; +} +Status EnergyEvseDelegate::HandleFaultCleared() +{ + /* Check that something strange hasn't happened */ + if ((mStateBeforeFault == StateEnum::kUnknownEnumValue) || (mSupplyStateBeforeFault == SupplyStateEnum::kUnknownEnumValue)) + { + ChipLogError(AppServer, "EVSE: Something wrong trying to clear fault"); + return Status::Failure; + } + + /* Restore the State and SupplyState back to old values once all the faults have cleared + * Changing the State should notify the application, so it can continue charging etc + */ + SetState(mStateBeforeFault); + SetSupplyState(mSupplyStateBeforeFault); + + /* put back the sentinel to catch new faults if more are raised */ + mStateBeforeFault = StateEnum::kUnknownEnumValue; + mSupplyStateBeforeFault = SupplyStateEnum::kUnknownEnumValue; + + return Status::Success; +} + /** * @brief Called to compute the safe charging current limit * @@ -507,6 +909,172 @@ Status EnergyEvseDelegate::NotifyApplicationStateChange() return Status::Success; } +Status EnergyEvseDelegate::GetEVSEEnergyMeterValue(ChargingDischargingType meterType, int64_t & aMeterValue) +{ + EVSECbInfo cbInfo; + + cbInfo.type = EVSECallbackType::EnergyMeterReadingRequested; + + cbInfo.EnergyMeterReadingRequest.meterType = meterType; + cbInfo.EnergyMeterReadingRequest.energyMeterValuePtr = &aMeterValue; + + if (mCallbacks.handler != nullptr) + { + mCallbacks.handler(&cbInfo, mCallbacks.arg); + } + + return Status::Success; +} + +Status EnergyEvseDelegate::SendEVConnectedEvent() +{ + Events::EVConnected::Type event; + EventNumber eventNumber; + + if (mSession.mSessionID.IsNull()) + { + ChipLogError(AppServer, "SessionID is Null"); + return Status::Failure; + } + + event.sessionID = mSession.mSessionID.Value(); + + CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format()); + return Status::Failure; + } + return Status::Success; +} + +Status EnergyEvseDelegate::SendEVNotDetectedEvent() +{ + Events::EVNotDetected::Type event; + EventNumber eventNumber; + + if (mSession.mSessionID.IsNull()) + { + ChipLogError(AppServer, "SessionID is Null"); + return Status::Failure; + } + + event.sessionID = mSession.mSessionID.Value(); + event.state = mState; + event.sessionDuration = mSession.mSessionDuration.Value(); + event.sessionEnergyCharged = mSession.mSessionEnergyCharged.Value(); + event.sessionEnergyDischarged = MakeOptional(mSession.mSessionEnergyDischarged.Value()); + + CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format()); + return Status::Failure; + } + return Status::Success; +} + +Status EnergyEvseDelegate::SendEnergyTransferStartedEvent() +{ + Events::EnergyTransferStarted::Type event; + EventNumber eventNumber; + + if (mSession.mSessionID.IsNull()) + { + ChipLogError(AppServer, "SessionID is Null"); + return Status::Failure; + } + + event.sessionID = mSession.mSessionID.Value(); + event.state = mState; + /** + * A positive value indicates the EV has been enabled for charging and the value is + * taken directly from the MaximumChargeCurrent attribute. + * A negative value indicates that the EV has been enabled for discharging and the value can be taken + * from the MaximumDischargeCurrent attribute with its sign inverted. + */ + + if (mState == StateEnum::kPluggedInCharging) + { + /* Sample the energy meter for charging */ + GetEVSEEnergyMeterValue(ChargingDischargingType::kCharging, mMeterValueAtEnergyTransferStart); + event.maximumCurrent = mMaximumChargeCurrent; + } + else if (mState == StateEnum::kPluggedInDischarging) + { + /* Sample the energy meter for discharging */ + GetEVSEEnergyMeterValue(ChargingDischargingType::kDischarging, mMeterValueAtEnergyTransferStart); + + /* discharging should have a negative current */ + event.maximumCurrent = -mMaximumDischargeCurrent; + } + + CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format()); + return Status::Failure; + } + + return Status::Success; +} +Status EnergyEvseDelegate::SendEnergyTransferStoppedEvent(EnergyTransferStoppedReasonEnum reason) +{ + Events::EnergyTransferStopped::Type event; + EventNumber eventNumber; + + if (mSession.mSessionID.IsNull()) + { + ChipLogError(AppServer, "SessionID is Null"); + return Status::Failure; + } + + event.sessionID = mSession.mSessionID.Value(); + event.state = mState; + event.reason = reason; + int64_t meterValueNow = 0; + + if (mState == StateEnum::kPluggedInCharging) + { + GetEVSEEnergyMeterValue(ChargingDischargingType::kCharging, meterValueNow); + event.energyTransferred = meterValueNow - mMeterValueAtEnergyTransferStart; + } + else if (mState == StateEnum::kPluggedInDischarging) + { + GetEVSEEnergyMeterValue(ChargingDischargingType::kDischarging, meterValueNow); + + /* discharging should have a negative value */ + event.energyTransferred = mMeterValueAtEnergyTransferStart - meterValueNow; + } + + CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format()); + return Status::Failure; + } + return Status::Success; +} + +Status EnergyEvseDelegate::SendFaultEvent(FaultStateEnum newFaultState) +{ + Events::Fault::Type event; + EventNumber eventNumber; + + event.sessionID = mSession.mSessionID; // Note here the event sessionID can be Null! + event.state = mState; // This is the state prior to the fault being raised + event.faultStatePreviousState = mFaultState; + event.faultStateCurrentState = newFaultState; + + CHIP_ERROR err = LogEvent(event, mEndpointId, eventNumber); + if (CHIP_NO_ERROR != err) + { + ChipLogError(AppServer, "Unable to send notify event: %" CHIP_ERROR_FORMAT, err.Format()); + return Status::Failure; + } + return Status::Success; +} + /** * Attribute methods */ @@ -529,6 +1097,7 @@ CHIP_ERROR EnergyEvseDelegate::SetState(StateEnum newValue) { ChipLogDetail(AppServer, "State updated to %d", static_cast(mState)); MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, State::Id); + NotifyApplicationStateChange(); } return CHIP_NO_ERROR; @@ -554,6 +1123,7 @@ CHIP_ERROR EnergyEvseDelegate::SetSupplyState(SupplyStateEnum newValue) { ChipLogDetail(AppServer, "SupplyState updated to %d", static_cast(mSupplyState)); MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SupplyState::Id); + NotifyApplicationStateChange(); } return CHIP_NO_ERROR; } @@ -588,17 +1158,30 @@ DataModel::Nullable EnergyEvseDelegate::GetChargingEnabledUntil() return mChargingEnabledUntil; } -CHIP_ERROR EnergyEvseDelegate::SetChargingEnabledUntil(uint32_t newValue) +CHIP_ERROR EnergyEvseDelegate::SetChargingEnabledUntil(DataModel::Nullable newValue) { DataModel::Nullable oldValue = mChargingEnabledUntil; - mChargingEnabledUntil = MakeNullable(newValue); - if ((oldValue.IsNull()) || (oldValue.Value() != newValue)) + mChargingEnabledUntil = newValue; + if (oldValue != newValue) { - ChipLogDetail(AppServer, "ChargingEnabledUntil updated to %lu", - static_cast(mChargingEnabledUntil.Value())); + if (newValue.IsNull()) + { + ChipLogDetail(AppServer, "ChargingEnabledUntil updated to Null"); + } + else + { + ChipLogDetail(AppServer, "ChargingEnabledUntil updated to %lu", + static_cast(mChargingEnabledUntil.Value())); + } + + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, ChargingEnabledUntil::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mChargingEnabledUntil); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, ChargingEnabledUntil::Id); } + return CHIP_NO_ERROR; } @@ -608,17 +1191,29 @@ DataModel::Nullable EnergyEvseDelegate::GetDischargingEnabledUntil() return mDischargingEnabledUntil; } -CHIP_ERROR EnergyEvseDelegate::SetDischargingEnabledUntil(uint32_t newValue) +CHIP_ERROR EnergyEvseDelegate::SetDischargingEnabledUntil(DataModel::Nullable newValue) { DataModel::Nullable oldValue = mDischargingEnabledUntil; - mDischargingEnabledUntil = MakeNullable(newValue); - if ((oldValue.IsNull()) || (oldValue.Value() != newValue)) + mDischargingEnabledUntil = newValue; + if (oldValue != newValue) { - ChipLogDetail(AppServer, "DischargingEnabledUntil updated to %lu", - static_cast(mDischargingEnabledUntil.Value())); + if (newValue.IsNull()) + { + ChipLogDetail(AppServer, "DischargingEnabledUntil updated to Null"); + } + else + { + ChipLogDetail(AppServer, "DischargingEnabledUntil updated to %lu", + static_cast(mDischargingEnabledUntil.Value())); + } + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, DischargingEnabledUntil::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mDischargingEnabledUntil); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, DischargingEnabledUntil::Id); } + return CHIP_NO_ERROR; } @@ -736,6 +1331,13 @@ CHIP_ERROR EnergyEvseDelegate::SetUserMaximumChargeCurrent(int64_t newValue) if (oldValue != newValue) { ChipLogDetail(AppServer, "UserMaximumChargeCurrent updated to %ld", static_cast(mUserMaximumChargeCurrent)); + + ComputeMaxChargeCurrentLimit(); + + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, UserMaximumChargeCurrent::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mUserMaximumChargeCurrent); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, UserMaximumChargeCurrent::Id); } @@ -761,6 +1363,11 @@ CHIP_ERROR EnergyEvseDelegate::SetRandomizationDelayWindow(uint32_t newValue) { ChipLogDetail(AppServer, "RandomizationDelayWindow updated to %lu", static_cast(mRandomizationDelayWindow)); + + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, RandomizationDelayWindow::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mRandomizationDelayWindow); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, RandomizationDelayWindow::Id); } return CHIP_NO_ERROR; @@ -798,14 +1405,25 @@ DataModel::Nullable EnergyEvseDelegate::GetApproximateEVEfficiency() return mApproximateEVEfficiency; } -CHIP_ERROR EnergyEvseDelegate::SetApproximateEVEfficiency(uint16_t newValue) +CHIP_ERROR EnergyEvseDelegate::SetApproximateEVEfficiency(DataModel::Nullable newValue) { DataModel::Nullable oldValue = mApproximateEVEfficiency; - mApproximateEVEfficiency = MakeNullable(newValue); - if ((oldValue.IsNull()) || (oldValue.Value() != newValue)) + mApproximateEVEfficiency = newValue; + if ((oldValue != newValue)) { - ChipLogDetail(AppServer, "ApproximateEVEfficiency updated to %d", mApproximateEVEfficiency.Value()); + if (newValue.IsNull()) + { + ChipLogDetail(AppServer, "ApproximateEVEfficiency updated to Null"); + } + else + { + ChipLogDetail(AppServer, "ApproximateEVEfficiency updated to %d", mApproximateEVEfficiency.Value()); + } + // Write new value to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, ApproximateEVEfficiency::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mApproximateEVEfficiency); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, ApproximateEVEfficiency::Id); } @@ -831,17 +1449,149 @@ DataModel::Nullable EnergyEvseDelegate::GetVehicleID() /* Session SESS attributes */ DataModel::Nullable EnergyEvseDelegate::GetSessionID() { - return mSessionID; + return mSession.mSessionID; } DataModel::Nullable EnergyEvseDelegate::GetSessionDuration() { - return mSessionDuration; + mSession.RecalculateSessionDuration(); + return mSession.mSessionDuration; } DataModel::Nullable EnergyEvseDelegate::GetSessionEnergyCharged() { - return mSessionEnergyCharged; + return mSession.mSessionEnergyCharged; } DataModel::Nullable EnergyEvseDelegate::GetSessionEnergyDischarged() { - return mSessionEnergyDischarged; + return mSession.mSessionEnergyDischarged; +} + +/** + * @brief Helper function to get current timestamp in Epoch format + * + * @param chipEpoch reference to hold return timestamp + */ +CHIP_ERROR GetEpochTS(uint32_t & chipEpoch) +{ + 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 + */ + VerifyOrDie(err != CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); + + 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; +} + +/** + * @brief This function samples the start-time, and energy meter to hold the session info + * + * @param chargingMeterValue - The current value of the energy meter (charging) in mWh + * @param dischargingMeterValue - The current value of the energy meter (discharging) in mWh + */ +void EvseSession::StartSession(int64_t chargingMeterValue, int64_t dischargingMeterValue) +{ + /* Get Timestamp */ + uint32_t chipEpoch = 0; + CHIP_ERROR err = GetEpochTS(chipEpoch); + if (err != CHIP_NO_ERROR) + { + /* Note that the error will be also be logged inside GetErrorTS() - + * adding context here to help debugging */ + ChipLogError(AppServer, "EVSE: Unable to get current time when starting session - err:%" CHIP_ERROR_FORMAT, err.Format()); + return; + } + mStartTime = chipEpoch; + + mSessionEnergyChargedAtStart = chargingMeterValue; + mSessionEnergyDischargedAtStart = dischargingMeterValue; + + if (mSessionID.IsNull()) + { + mSessionID = MakeNullable(static_cast(0)); + } + else + { + uint32_t sessionID = mSessionID.Value() + 1; + mSessionID = MakeNullable(sessionID); + } + + /* Reset other session values */ + mSessionDuration = MakeNullable(static_cast(0)); + mSessionEnergyCharged = MakeNullable(static_cast(0)); + mSessionEnergyDischarged = MakeNullable(static_cast(0)); + + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionID::Id); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionDuration::Id); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyCharged::Id); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); + + // Write values to persistent storage. + ConcreteAttributePath path = ConcreteAttributePath(mEndpointId, EnergyEvse::Id, SessionID::Id); + GetSafeAttributePersistenceProvider()->WriteScalarValue(path, mSessionID); + + // TODO persist mStartTime + // TODO persist mSessionEnergyChargedAtStart + // TODO persist mSessionEnergyDischargedAtStart +} + +/** + * @brief This function updates the session attrs to allow read attributes to return latest values + */ +void EvseSession::RecalculateSessionDuration() +{ + /* Get Timestamp */ + uint32_t chipEpoch = 0; + CHIP_ERROR err = GetEpochTS(chipEpoch); + if (err != CHIP_NO_ERROR) + { + /* Note that the error will be also be logged inside GetErrorTS() - + * adding context here to help debugging */ + ChipLogError(AppServer, "EVSE: Unable to get current time when updating session duration - err:%" CHIP_ERROR_FORMAT, + err.Format()); + return; + } + + uint32_t duration = chipEpoch - mStartTime; + mSessionDuration = MakeNullable(duration); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionDuration::Id); +} + +/** + * @brief This function updates the EnergyCharged meter value + * + * @param chargingMeterValue - The value of the energy meter (charging) in mWh + */ +void EvseSession::UpdateEnergyCharged(int64_t chargingMeterValue) +{ + mSessionEnergyCharged = MakeNullable(chargingMeterValue - mSessionEnergyChargedAtStart); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyCharged::Id); +} + +/** + * @brief This function updates the EnergyDischarged meter value + * + * @param dischargingMeterValue - The value of the energy meter (discharging) in mWh + */ +void EvseSession::UpdateEnergyDischarged(int64_t dischargingMeterValue) +{ + mSessionEnergyDischarged = MakeNullable(dischargingMeterValue - mSessionEnergyDischargedAtStart); + MatterReportingAttributeChangeCallback(mEndpointId, EnergyEvse::Id, SessionEnergyDischarged::Id); } 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 0d84d8856212e0..710b13f9d342b8 100644 --- a/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp +++ b/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp @@ -17,14 +17,96 @@ */ #include +#include using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; +CHIP_ERROR EnergyEvseManager::LoadPersistentAttributes() +{ + + SafeAttributePersistenceProvider * aProvider = GetSafeAttributePersistenceProvider(); + EndpointId aEndpointId = mDelegate->GetEndpointId(); + CHIP_ERROR err; + + // Restore ChargingEnabledUntil value + DataModel::Nullable tempChargingEnabledUntil; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::ChargingEnabledUntil::Id), + tempChargingEnabledUntil); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded ChargingEnabledUntil from NVM"); + mDelegate->SetChargingEnabledUntil(tempChargingEnabledUntil); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted ChargingEnabledUntil value"); + } + + // Restore DischargingEnabledUntil value + DataModel::Nullable tempDischargingEnabledUntil; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::DischargingEnabledUntil::Id), + tempDischargingEnabledUntil); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded DischargingEnabledUntil from NVM"); + mDelegate->SetDischargingEnabledUntil(tempDischargingEnabledUntil); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted DischargingEnabledUntil value"); + } + + // Restore UserMaximumChargeCurrent value + int64_t tempUserMaximumChargeCurrent; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::UserMaximumChargeCurrent::Id), + tempUserMaximumChargeCurrent); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded UserMaximumChargeCurrent from NVM"); + mDelegate->SetUserMaximumChargeCurrent(tempUserMaximumChargeCurrent); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted UserMaximumChargeCurrent value"); + } + + // Restore RandomizationDelayWindow value + uint32_t tempRandomizationDelayWindow; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::RandomizationDelayWindow::Id), + tempRandomizationDelayWindow); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded RandomizationDelayWindow from NVM"); + mDelegate->SetRandomizationDelayWindow(tempRandomizationDelayWindow); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted RandomizationDelayWindow value"); + } + + // Restore ApproximateEVEfficiency value + DataModel::Nullable tempApproxEVEfficiency; + err = aProvider->ReadScalarValue(ConcreteAttributePath(aEndpointId, EnergyEvse::Id, Attributes::ApproximateEVEfficiency::Id), + tempApproxEVEfficiency); + if (err == CHIP_NO_ERROR) + { + ChipLogDetail(AppServer, "EVSE: successfully loaded ApproximateEVEfficiency from NVM"); + mDelegate->SetApproximateEVEfficiency(tempApproxEVEfficiency); + } + else + { + ChipLogError(AppServer, "EVSE: Unable to restore persisted ApproximateEVEfficiency value"); + } + + return CHIP_NO_ERROR; // It is ok to have no value loaded here +} + CHIP_ERROR EnergyEvseManager::Init() { - return Instance::Init(); + ReturnErrorOnFailure(Instance::Init()); + return LoadPersistentAttributes(); } void EnergyEvseManager::Shutdown() diff --git a/examples/energy-management-app/linux/BUILD.gn b/examples/energy-management-app/linux/BUILD.gn index 8597caabfc35b9..8fb53878129c60 100644 --- a/examples/energy-management-app/linux/BUILD.gn +++ b/examples/energy-management-app/linux/BUILD.gn @@ -36,6 +36,8 @@ config("includes") { executable("chip-energy-management-app") { sources = [ + "${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/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", diff --git a/examples/energy-management-app/linux/README.md b/examples/energy-management-app/linux/README.md index 9671ab073b12a8..be4e265e45b37d 100644 --- a/examples/energy-management-app/linux/README.md +++ b/examples/energy-management-app/linux/README.md @@ -13,10 +13,17 @@ To cross-compile this example on x64 host and run on **NXP i.MX 8M Mini** - [CHIP Linux Energy Management Example](#chip-linux-energy-management-example) - [Building](#building) - - [Commandline Arguments](#commandline-arguments) + - [Commandline arguments](#commandline-arguments) - [Running the Complete Example on Raspberry Pi 4](#running-the-complete-example-on-raspberry-pi-4) - - [Running RPC console](#running-rpc-console) + - [Running RPC Console](#running-rpc-console) - [Device Tracing](#device-tracing) + - [Python Test Cases](#python-test-cases) + - [Running the test cases:](#running-the-test-cases) + - [CHIP-REPL Interaction](#chip-repl-interaction) + - [Building chip-repl:](#building-chip-repl) + - [Activating python virtual env](#activating-python-virtual-env) + - [Interacting with CHIP-REPL and the example app](#interacting-with-chip-repl-and-the-example-app) + - [Using chip-repl to Fake a charging session](#using-chip-repl-to-fake-a-charging-session)
@@ -141,3 +148,361 @@ Obtain tracing json file. $ ./{PIGWEED_REPO}/pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py -s localhost:33000 \ -o {OUTPUT_FILE} -t {ELF_FILE} {PIGWEED_REPO}/pw_trace_tokenized/pw_trace_protos/trace_rpc.proto ``` + +## Python Test Cases + +When you want to test this cluster you can use chip-repl or chip-tool by hand. +CHIP-REPL is slightly easier to interact with when dealing with some of the +complex structures. + +There are several test scripts provided for EVSE (in +[src/python_testing](src/python_testing)): + +- `TC_EEVSE_2_2`: This validates the primary functionality +- `TC_EEVSE_2_3`: This validates Get/Set/Clear target commands +- `TC_EEVSE_2_4`: This validates Faults +- `TC_EEVSE_2_5`: This validates EVSE diagnostic command (optional) + +These scripts require the use of Test Event Triggers via the GeneralDiagnostics +cluster on Endpoint 0. This requires an `enableKey` (16 bytes) and a set of +reserved int64_t test event trigger codes. + +By default the test event support is not enabled, and when compiling the example +app you need to add `chip_enable_energy_evse_trigger=true` to the gn args. + + $ gn gen out/debug + $ ninja -C out/debug --args='chip_enable_energy_evse_trigger=true' + +Once the application is built you also need to tell it at runtime what the +chosen enable key is using the `--enable-key` command line option. + + $ ./chip-energy-management-app --enable-key 000102030405060708090a0b0c0d0e0f + +### Running the test cases: + +From the top-level of the connectedhomeip repo type: + +```bash + $ python src/python_testing/TC_EEVSE_2_2.py --endpoint 1 -m on-network -n 1234 -p 20202021 -d 3840 --hex-arg enableKey:000102030405060708090a0b0c0d0e0f +``` + +- Note that the `--endpoint 1` must be used with the example, since the EVSE + cluster is on endpoint 1. The `--hex-arg enableKey:` value must match + the `--enable-key ` used on chip-energy-management-app args. + +## CHIP-REPL Interaction + +- See chip-repl documentation in + [Matter_REPL_Intro](../../../docs/guides/repl/Matter_REPL_Intro.ipynb) + +### Building chip-repl: + +```bash + $ ./build_python.sh -i +``` + +### Activating python virtual env + +- You need to repeat this step each time you start a new shell. + +```bash + $ source /bin/activate +``` + +### Interacting with CHIP-REPL and the example app + +- Step 1: Launch the example app + +```bash + $ ./chip-energy-management-app --enable-key 000102030405060708090a0b0c0d0e0f +``` + +- Step 2: Launch CHIP-REPL + +```bash + $ chip-repl +``` + +- Step 3: (In chip-repl) Commissioning OnNetwork + +```python + devCtrl.CommissionOnNetwork(1234,20202021) # Commission with NodeID 1234 +Established secure session with Device +Commissioning complete +Out[2]: +``` + +- Step 4: (In chip-repl) Read EVSE attributes + +```python + # Read from NodeID 1234, Endpoint 1, all attributes on EnergyEvse cluster + await devCtrl.ReadAttribute(1234,[(1, chip.clusters.EnergyEvse)]) +``` + +``` +{ +│ 1: { +│ │ : { +│ │ │ : 3790455237, +│ │ │ : Null, +│ │ │ : , +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : 0, +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : [ +... │ │ ], +│ │ │ : 6000, +│ │ │ : Null, +│ │ │ : 758415333, +│ │ │ : 0, +│ │ │ : 1, +│ │ │ : [ +... +│ │ │ ], +│ │ │ : , +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : Null, +│ │ │ : [ +... │ │ ], +│ │ │ : Null, +│ │ │ : 0, +│ │ │ : Null, +│ │ │ : , +│ │ │ : 600, +│ │ │ : 0, +│ │ │ : 1, +│ │ │ : 80000, +│ │ │ : 2 +│ │ } +│ } +} + +``` + +- Step 5: Setting up a subscription so that attributes updates are sent + automatically + +```python + reportingTimingParams = (3, 60) # MinInterval = 3s, MaxInterval = 60s + subscription = await devCtrl.ReadAttribute(1234,[(1, chip.clusters.EnergyEvse)], reportInterval=reportingTimingParams) +``` + +- Step 6: Send an `EnableCharging` command which lasts for 60 seconds The + `EnableCharging` takes an optional `chargingEnabledUntil` parameter which + allows the charger to automatically disable itself at some preset time in + the future. Note that it uses Epoch_s (which is from Jan 1 2000) which is a + uint32_t in seconds. + +```python + from datetime import datetime, timezone, timedelta + epoch_end = int((datetime.now(tz=timezone.utc) + timedelta(seconds=60) - datetime(2000, 1, 1, 0, 0, 0, 0, timezone.utc)).total_seconds()) + + await devCtrl.SendCommand(1234, 1, chip.clusters.EnergyEvse.Commands.EnableCharging(chargingEnabledUntil=epoch_end,minimumChargeCurrent=2000,maximumChargeCurrent=25000),timedRequestTimeoutMs=3000) +``` + +The output should look like: + +``` +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 2000 +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 758416066 +} +``` + +After 60 seconds the charging should automatically become disabled: + +``` +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 0 +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 0 +} +Attribute Changed: +{ +│ 'Endpoint': 1, +│ 'Attribute': , +│ 'Value': 0 +} +``` + +Note that you can omit the `chargingEnabledUntil` argument and it will charge +indefinitely. + +### Using chip-repl to Fake a charging session + +If you haven't implemented a real EVSE but want to simulate plugging in an EV +then you can use a few of the test event triggers to simulate these scenarios. + +The test event triggers values can be found in: +[EnergyEvseTestEventTriggerDelegate.h](../../../src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h) + +- 0x0099000000000000 - Simulates the EVSE being installed on a 32A supply +- 0x0099000000000002 - Simulates the EVSE being plugged in (this should + generate an `EVConnected` event) +- 0x0099000000000004 - Simulates the EVSE requesting power + +To send a test event trigger to the app, use the following commands (in +chip-repl): + +```python + # send 1st event trigger to 'install' the EVSE on a 32A supply + await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000000)) + + # send 2nd event trigger to plug the EV in + await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000002)) + +``` + +Now send the enable charging command (omit the `chargingEnabledUntil` arg this +time): + +```python + await devCtrl.SendCommand(1234, 1, chip.clusters.EnergyEvse.Commands.EnableCharging(minimumChargeCurrent=2000,maximumChargeCurrent=25000),timedRequestTimeoutMs=3000) +``` + +Now send the test event trigger to simulate the EV asking for demand: + +```python + # send 2nd event trigger to plug the EV in + await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000004)) + + # Read the events + await devCtrl.ReadEvent(1234,[(1, chip.clusters.EnergyEvse,1)]) +``` + +``` +[ +│ EventReadResult( +│ │ Header=EventHeader( +│ │ │ EndpointId=1, +│ │ │ ClusterId=153, +│ │ │ EventId=0, +│ │ │ EventNumber=65538, +│ │ │ Priority=, +│ │ │ Timestamp=1705102500069, +│ │ │ TimestampType= +│ │ ), +│ │ Status=, +│ │ Data=EVConnected( +│ │ │ sessionID=0 +│ │ ) +│ ), +│ EventReadResult( +│ │ Header=EventHeader( +│ │ │ EndpointId=1, +│ │ │ ClusterId=153, +│ │ │ EventId=2, +│ │ │ EventNumber=65539, +│ │ │ Priority=, +│ │ │ Timestamp=1705102801764, +│ │ │ TimestampType= +│ │ ), +│ │ Status=, +│ │ Data=EnergyTransferStarted( +│ │ │ sessionID=0, +│ │ │ state=, +│ │ │ maximumCurrent=25000 +│ │ ) +│ ) +] +``` + +- We can see that the `EventNumber 65538` was sent when the vehicle was + plugged in, and a new `sessionID=0` was created. +- We can also see that the `EnergyTransferStarted` was sent in + `EventNumber 65539` + +What happens when we unplug the vehicle? + +```python + await devCtrl.SendCommand(1234, 0, chip.clusters.GeneralDiagnostics.Commands.TestEventTrigger(enableKey=bytes([b for b in range(16)]), eventTrigger=0x0099000000000001)) +``` + +When we re-read the events: + +``` +[ +│ EventReadResult( +│ │ Header=EventHeader( +│ │ │ EndpointId=1, +│ │ │ ClusterId=153, +│ │ │ EventId=3, +│ │ │ EventNumber=65540, +│ │ │ Priority=, +│ │ │ Timestamp=1705102996749, +│ │ │ TimestampType= +│ │ ), +│ │ Status=, +│ │ Data=EnergyTransferStopped( +│ │ │ sessionID=0, +│ │ │ state=, +│ │ │ reason=, +│ │ │ energyTransferred=0 +│ │ ) +│ ), +│ EventReadResult( +│ │ Header=EventHeader( +│ │ │ EndpointId=1, +│ │ │ ClusterId=153, +│ │ │ EventId=1, +│ │ │ EventNumber=65541, +│ │ │ Priority=, +│ │ │ Timestamp=1705102996749, +│ │ │ TimestampType= +│ │ ), +│ │ Status=, +│ │ Data=EVNotDetected( +│ │ │ sessionID=0, +│ │ │ state=, +│ │ │ sessionDuration=0, +│ │ │ sessionEnergyCharged=0, +│ │ │ sessionEnergyDischarged=0 +│ │ ) +│ ) +] + +``` + +- In `EventNumber 65540` we had an `EnergyTransferStopped` event with reason + `kOther`. + + This was a rather abrupt end to a charging session (normally we would see + the EVSE or EV decide to stop charging), but this demonstrates the cable + being pulled out without a graceful charging shutdown. + +- In `EventNumber 65541` we had an `EvNotDetected` event showing that the + state was `kPluggedInCharging` prior to the EV being not detected (normally + in a graceful shutdown this would be `kPluggedInNoDemand` or + `kPluggedInDemand`). diff --git a/examples/energy-management-app/linux/include/CHIPProjectAppConfig.h b/examples/energy-management-app/linux/include/CHIPProjectAppConfig.h index f22628d0f69def..e091fa9cf7c91c 100644 --- a/examples/energy-management-app/linux/include/CHIPProjectAppConfig.h +++ b/examples/energy-management-app/linux/include/CHIPProjectAppConfig.h @@ -32,15 +32,13 @@ #define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 0 -// Bulbs do not typically use this - enabled so we can use shell to discover commissioners #define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT 1 #define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 #define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONABLE_DEVICE_TYPE 1 -// TODO We don’t have one yet - but we’d need to change this -#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 257 // 0x0101 = 257 = Dimmable Bulb +#define CHIP_DEVICE_CONFIG_DEVICE_TYPE 0x050C // Energy EVSE #define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONABLE_DEVICE_NAME 1 diff --git a/examples/energy-management-app/linux/main.cpp b/examples/energy-management-app/linux/main.cpp index dddc6f53d76b20..23ec97e267f45d 100644 --- a/examples/energy-management-app/linux/main.cpp +++ b/examples/energy-management-app/linux/main.cpp @@ -17,6 +17,7 @@ */ #include +#include #include #include #include @@ -35,80 +36,227 @@ using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; -static EnergyEvseDelegate * gDelegate = nullptr; -static EnergyEvseManager * gInstance = nullptr; -static EVSEManufacturer * gEvseManufacturer = nullptr; +static std::unique_ptr gEvseDelegate; +static std::unique_ptr gEvseInstance; +static std::unique_ptr gDEMDelegate; +static std::unique_ptr gDEMInstance; +static std::unique_ptr gEvseManufacturer; -void ApplicationInit() +EVSEManufacturer * EnergyEvse::GetEvseManufacturer() +{ + return gEvseManufacturer.get(); +} + +/* + * @brief Creates a Delegate and Instance for DEM + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR DeviceEnergyManagementInit() +{ + if (gDEMDelegate || gDEMInstance) + { + ChipLogError(AppServer, "DEM Instance or Delegate already exist."); + return CHIP_ERROR_INCORRECT_STATE; + } + + gDEMDelegate = std::make_unique(); + if (!gDEMDelegate) + { + ChipLogError(AppServer, "Failed to allocate memory for DeviceEnergyManagementDelegate"); + return CHIP_ERROR_NO_MEMORY; + } + + /* Manufacturer may optionally not support all features, commands & attributes */ + gDEMInstance = std::make_unique( + EndpointId(ENERGY_EVSE_ENDPOINT), *gDEMDelegate, + BitMask( + DeviceEnergyManagement::Feature::kPowerForecastReporting, DeviceEnergyManagement::Feature::kStateForecastReporting, + DeviceEnergyManagement::Feature::kPowerAdjustment, DeviceEnergyManagement::Feature::kForecastAdjustment), + BitMask( + DeviceEnergyManagement::OptionalCommands::kSupportsModifyForecastRequest, + DeviceEnergyManagement::OptionalCommands::kSupportsRequestConstraintBasedForecast)); + + if (!gDEMInstance) + { + ChipLogError(AppServer, "Failed to allocate memory for DeviceEnergyManagementManager"); + gDEMDelegate.reset(); + return CHIP_ERROR_NO_MEMORY; + } + + CHIP_ERROR err = gDEMInstance->Init(); /* Register Attribute & Command handlers */ + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "Init failed on gDEMInstance"); + gDEMInstance.reset(); + gDEMDelegate.reset(); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR DeviceEnergyManagementShutdown() +{ + /* Do this in the order Instance first, then delegate + * Ensure we call the Instance->Shutdown to free attribute & command handlers first + */ + if (gDEMInstance) + { + /* deregister attribute & command handlers */ + gDEMInstance->Shutdown(); + gDEMInstance.reset(); + } + if (gDEMDelegate) + { + gDEMDelegate.reset(); + } + return CHIP_NO_ERROR; +} + +/* + * @brief Creates a Delegate and Instance for EVSE cluster + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR EnergyEvseInit() { CHIP_ERROR err; - if ((gDelegate != nullptr) || (gInstance != nullptr) || (gEvseManufacturer != nullptr)) + if (gEvseDelegate || gEvseInstance) { - ChipLogError(AppServer, "EVSE Instance or Delegate, EvseManufacturer already exist."); - return; + ChipLogError(AppServer, "EVSE Instance or Delegate already exist."); + return CHIP_ERROR_INCORRECT_STATE; } - gDelegate = new EnergyEvseDelegate(); - if (gDelegate == nullptr) + gEvseDelegate = std::make_unique(); + if (!gEvseDelegate) { ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseDelegate"); - return; + return CHIP_ERROR_NO_MEMORY; } /* Manufacturer may optionally not support all features, commands & attributes */ - gInstance = - new EnergyEvseManager(EndpointId(ENERGY_EVSE_ENDPOINT), *gDelegate, - BitMask( - EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, - EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, EnergyEvse::Feature::kV2x), - BitMask(OptionalAttributes::kSupportsUserMaximumChargingCurrent, - OptionalAttributes::kSupportsRandomizationWindow, - OptionalAttributes::kSupportsApproximateEvEfficiency), - BitMask(OptionalCommands::kSupportsStartDiagnostics)); - - if (gInstance == nullptr) + gEvseInstance = std::make_unique( + EndpointId(ENERGY_EVSE_ENDPOINT), *gEvseDelegate, + BitMask(EnergyEvse::Feature::kChargingPreferences, EnergyEvse::Feature::kPlugAndCharge, + EnergyEvse::Feature::kRfid, EnergyEvse::Feature::kSoCReporting, + EnergyEvse::Feature::kV2x), + BitMask(EnergyEvse::OptionalAttributes::kSupportsUserMaximumChargingCurrent, + EnergyEvse::OptionalAttributes::kSupportsRandomizationWindow, + EnergyEvse::OptionalAttributes::kSupportsApproximateEvEfficiency), + BitMask(EnergyEvse::OptionalCommands::kSupportsStartDiagnostics)); + + if (!gEvseInstance) { ChipLogError(AppServer, "Failed to allocate memory for EnergyEvseManager"); - delete gDelegate; - gDelegate = nullptr; - return; + gEvseDelegate.reset(); + return CHIP_ERROR_NO_MEMORY; } - err = gInstance->Init(); /* Register Attribute & Command handlers */ + err = gEvseInstance->Init(); /* Register Attribute & Command handlers */ if (err != CHIP_NO_ERROR) { - ChipLogError(AppServer, "Init failed on gInstance"); - delete gInstance; - delete gDelegate; - gInstance = nullptr; - gDelegate = nullptr; - return; + ChipLogError(AppServer, "Init failed on gEvseInstance"); + gEvseInstance.reset(); + gEvseDelegate.reset(); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR EnergyEvseShutdown() +{ + /* Do this in the order Instance first, then delegate + * Ensure we call the Instance->Shutdown to free attribute & command handlers first + */ + if (gEvseInstance) + { + /* deregister attribute & command handlers */ + gEvseInstance->Shutdown(); + gEvseInstance.reset(); } - /* Now create EVSEManufacturer*/ - gEvseManufacturer = new EVSEManufacturer(); - if (gEvseManufacturer == nullptr) + if (gEvseDelegate) + { + gEvseDelegate.reset(); + } + + return CHIP_NO_ERROR; +} + +/* + * @brief Creates a EVSEManufacturer class to hold the EVSE & DEM clusters + * + * The Instance is a container around the Delegate, so + * create the Delegate first, then wrap it in the Instance + * Then call the Instance->Init() to register the attribute and command handlers + */ +CHIP_ERROR EVSEManufacturerInit() +{ + CHIP_ERROR err; + + if (gEvseManufacturer) + { + ChipLogError(AppServer, "EvseManufacturer already exist."); + return CHIP_ERROR_INCORRECT_STATE; + } + + /* Now create EVSEManufacturer */ + // TODO this takes just the EVSE Instance for now, but will need the DEM adding + gEvseManufacturer = std::make_unique(gEvseInstance.get()); + if (!gEvseManufacturer) { ChipLogError(AppServer, "Failed to allocate memory for EvseManufacturer"); - delete gInstance; - delete gDelegate; - gInstance = nullptr; - gDelegate = nullptr; - return; + return CHIP_ERROR_NO_MEMORY; } /* Call Manufacturer specific init */ - err = gEvseManufacturer->Init(gInstance); + err = gEvseManufacturer->Init(); if (err != CHIP_NO_ERROR) { ChipLogError(AppServer, "Init failed on gEvseManufacturer"); - delete gEvseManufacturer; - delete gInstance; - delete gDelegate; - gEvseManufacturer = nullptr; - gInstance = nullptr; - gDelegate = nullptr; + gEvseManufacturer.reset(); + return err; + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR EVSEManufacturerShutdown() +{ + if (gEvseManufacturer) + { + /* Shutdown the EVSEManufacturer */ + gEvseManufacturer->Shutdown(); + gEvseManufacturer.reset(); + } + + return CHIP_NO_ERROR; +} + +void ApplicationInit() +{ + if (DeviceEnergyManagementInit() != CHIP_NO_ERROR) + { + return; + } + + if (EnergyEvseInit() != CHIP_NO_ERROR) + { + DeviceEnergyManagementShutdown(); + return; + } + + if (EVSEManufacturerInit() != CHIP_NO_ERROR) + { + DeviceEnergyManagementShutdown(); + EnergyEvseShutdown(); return; } } @@ -117,18 +265,10 @@ void ApplicationShutdown() { ChipLogDetail(AppServer, "Energy Management App: ApplicationShutdown()"); - /* Shutdown the EVSEManufacturer*/ - gEvseManufacturer->Shutdown(gInstance); - - /* Shutdown the Instance - deregister attribute & command handler */ - gInstance->Shutdown(); - - delete gEvseManufacturer; - delete gInstance; - delete gDelegate; - gEvseManufacturer = nullptr; - gInstance = nullptr; - gDelegate = nullptr; + /* Shutdown in reverse order that they were created */ + EVSEManufacturerShutdown(); /* Free the EVSEManufacturer */ + EnergyEvseShutdown(); /* Free the EnergyEvse */ + DeviceEnergyManagementShutdown(); /* Free the DEM */ } int main(int argc, char * argv[]) diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 86382f75665c06..56098c7c490548 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -83,6 +83,9 @@ #if CHIP_DEVICE_CONFIG_ENABLE_BOOLEAN_STATE_CONFIGURATION_TRIGGER #include #endif +#if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER +#include +#endif #include #include @@ -561,6 +564,13 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) }; otherDelegate = &booleanStateConfigurationTestEventTriggerDelegate; #endif +#if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER + static EnergyEvseTestEventTriggerDelegate energyEvseTestEventTriggerDelegate{ + ByteSpan(LinuxDeviceOptions::GetInstance().testEventTriggerEnableKey), otherDelegate + }; + otherDelegate = &energyEvseTestEventTriggerDelegate; +#endif + // For general testing of TestEventTrigger, we have a common "core" event trigger delegate. static SampleTestEventTriggerDelegate testEventTriggerDelegate; VerifyOrDie(testEventTriggerDelegate.Init(ByteSpan(LinuxDeviceOptions::GetInstance().testEventTriggerEnableKey), diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index 4a4099c4150bf5..fbbf7e1833894e 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -21,8 +21,8 @@ import("${chip_root}/src/tracing/tracing_args.gni") declare_args() { chip_enable_smoke_co_trigger = false - chip_enable_boolean_state_configuration_trigger = false + chip_enable_energy_evse_trigger = false } config("app-main-config") { @@ -43,6 +43,10 @@ source_set("boolean-state-configuration-test-event-trigger") { sources = [ "${chip_root}/src/app/clusters/boolean-state-configuration-server/BooleanStateConfigurationTestEventTriggerDelegate.h" ] } +source_set("energy-evse-test-event-trigger") { + sources = [ "${chip_root}/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h" ] +} + source_set("app-main") { defines = [ "ENABLE_TRACING=${matter_enable_tracing_support}" ] sources = [ @@ -66,6 +70,7 @@ source_set("app-main") { public_deps = [ ":boolean-state-configuration-test-event-trigger", + ":energy-evse-test-event-trigger", ":smco-test-event-trigger", "${chip_root}/src/lib", "${chip_root}/src/platform/logging:force_stdio", @@ -104,6 +109,7 @@ source_set("app-main") { defines += [ "CHIP_DEVICE_CONFIG_ENABLE_SMOKE_CO_TRIGGER=${chip_enable_smoke_co_trigger}", "CHIP_DEVICE_CONFIG_ENABLE_BOOLEAN_STATE_CONFIGURATION_TRIGGER=${chip_enable_boolean_state_configuration_trigger}", + "CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER=${chip_enable_energy_evse_trigger}", ] public_configs = [ ":app-main-config" ] diff --git a/examples/shell/shell_common/BUILD.gn b/examples/shell/shell_common/BUILD.gn index a54df29c798955..28b26af3ae211f 100644 --- a/examples/shell/shell_common/BUILD.gn +++ b/examples/shell/shell_common/BUILD.gn @@ -65,10 +65,13 @@ static_library("shell_common") { sources += [ "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/device-energy-management-stub.cpp", "${chip_root}/examples/all-clusters-app/all-clusters-common/src/energy-evse-stub.cpp", "${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/DeviceEnergyManagementDelegateImpl.cpp", + "${chip_root}/examples/energy-management-app/energy-management-common/src/DeviceEnergyManagementManager.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseDelegateImpl.cpp", "${chip_root}/examples/energy-management-app/energy-management-common/src/EnergyEvseManager.cpp", ] diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index a22e4811433802..4924ebfa67835d 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -336,6 +336,13 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/${cluster}.cpp", "${_app_root}/clusters/${cluster}/${cluster}.h", ] + } else if (cluster == "energy-evse-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/${cluster}.h", + "${_app_root}/clusters/${cluster}/EnergyEvseTestEventTriggerDelegate.cpp", + "${_app_root}/clusters/${cluster}/EnergyEvseTestEventTriggerDelegate.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } diff --git a/src/app/clusters/device-energy-management-server/device-energy-management-server.cpp b/src/app/clusters/device-energy-management-server/device-energy-management-server.cpp new file mode 100644 index 00000000000000..bfe3cac5f2e89e --- /dev/null +++ b/src/app/clusters/device-energy-management-server/device-energy-management-server.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "device-energy-management-server.h" + +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::DeviceEnergyManagement; +using namespace chip::app::Clusters::DeviceEnergyManagement::Attributes; + +using chip::Protocols::InteractionModel::Status; + +namespace chip { +namespace app { +namespace Clusters { +namespace DeviceEnergyManagement { + +CHIP_ERROR Instance::Init() +{ + ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->RegisterCommandHandler(this)); + VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INCORRECT_STATE); + + return CHIP_NO_ERROR; +} + +void Instance::Shutdown() +{ + InteractionModelEngine::GetInstance()->UnregisterCommandHandler(this); + unregisterAttributeAccessOverride(this); +} + +bool Instance::HasFeature(Feature aFeature) const +{ + return mFeature.Has(aFeature); +} + +bool Instance::SupportsOptCmd(OptionalCommands aOptionalCmds) const +{ + return mOptionalCmds.Has(aOptionalCmds); +} + +// AttributeAccessInterface +CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + switch (aPath.mAttributeId) + { + case ESAType::Id: + return aEncoder.Encode(mDelegate.GetESAType()); + case ESACanGenerate::Id: + return aEncoder.Encode(mDelegate.GetESACanGenerate()); + case ESAState::Id: + return aEncoder.Encode(mDelegate.GetESAState()); + case AbsMinPower::Id: + return aEncoder.Encode(mDelegate.GetAbsMinPower()); + case AbsMaxPower::Id: + return aEncoder.Encode(mDelegate.GetAbsMaxPower()); + case PowerAdjustmentCapability::Id: + /* PA - PowerAdjustment */ + if (!HasFeature(Feature::kPowerAdjustment)) + { + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } + return aEncoder.Encode(mDelegate.GetPowerAdjustmentCapability()); + case Forecast::Id: + /* PFR | SFR - Power Forecast Reporting or State Forecast Reporting */ + if (!HasFeature(Feature::kPowerForecastReporting) && !HasFeature(Feature::kStateForecastReporting)) + { + return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); + } + return aEncoder.Encode(mDelegate.GetForecast()); + /* FeatureMap - is held locally */ + case FeatureMap::Id: + return aEncoder.Encode(mFeature); + } + + /* Allow all other unhandled attributes to fall through to Ember */ + return CHIP_NO_ERROR; +} + +// CommandHandlerInterface +CHIP_ERROR Instance::EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) +{ + using namespace Commands; + + if (HasFeature(Feature::kPowerAdjustment)) + { + for (auto && cmd : { + PowerAdjustRequest::Id, + CancelPowerAdjustRequest::Id, + }) + { + VerifyOrExit(callback(cmd, context) == Loop::Continue, /**/); + } + } + + if (HasFeature(Feature::kForecastAdjustment)) + { + for (auto && cmd : { + StartTimeAdjustRequest::Id, + PauseRequest::Id, + ResumeRequest::Id, + }) + { + VerifyOrExit(callback(cmd, context) == Loop::Continue, /**/); + } + } + + if (SupportsOptCmd(OptionalCommands::kSupportsModifyForecastRequest)) + { + VerifyOrExit(callback(ModifyForecastRequest::Id, context) == Loop::Continue, /**/); + } + + if (SupportsOptCmd(OptionalCommands::kSupportsRequestConstraintBasedForecast)) + { + VerifyOrExit(callback(RequestConstraintBasedForecast::Id, context) == Loop::Continue, /**/); + } + +exit: + return CHIP_NO_ERROR; +} + +void Instance::InvokeCommand(HandlerContext & handlerContext) +{ + using namespace Commands; + + switch (handlerContext.mRequestPath.mCommandId) + { + case PowerAdjustRequest::Id: + if (!HasFeature(Feature::kPowerAdjustment)) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); + } + else + { + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandlePowerAdjustRequest(ctx, commandData); }); + } + return; + case CancelPowerAdjustRequest::Id: + if (!HasFeature(Feature::kPowerAdjustment)) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); + } + else + { + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleCancelPowerAdjustRequest(ctx, commandData); }); + } + return; + case StartTimeAdjustRequest::Id: + if (!HasFeature(Feature::kForecastAdjustment)) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); + } + else + { + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleStartTimeAdjustRequest(ctx, commandData); }); + } + return; + case PauseRequest::Id: + if (!HasFeature(Feature::kForecastAdjustment)) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); + } + else + { + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandlePauseRequest(ctx, commandData); }); + } + return; + case ResumeRequest::Id: + if (!HasFeature(Feature::kForecastAdjustment)) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); + } + else + { + HandleCommand( + handlerContext, [this](HandlerContext & ctx, const auto & commandData) { HandleResumeRequest(ctx, commandData); }); + } + return; + case ModifyForecastRequest::Id: + if (!SupportsOptCmd(OptionalCommands::kSupportsModifyForecastRequest)) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); + } + else + { + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleModifyForecastRequest(ctx, commandData); }); + } + return; + case RequestConstraintBasedForecast::Id: + if (!SupportsOptCmd(OptionalCommands::kSupportsRequestConstraintBasedForecast)) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::UnsupportedCommand); + } + else + { + HandleCommand( + handlerContext, + [this](HandlerContext & ctx, const auto & commandData) { HandleRequestConstraintBasedForecast(ctx, commandData); }); + } + return; + } +} + +void Instance::HandlePowerAdjustRequest(HandlerContext & ctx, const Commands::PowerAdjustRequest::DecodableType & commandData) +{ + int64_t power = commandData.power; + uint32_t durationSec = commandData.duration; + bool validArgs = false; + Status status = Status::Success; + + PowerAdjustmentCapability::TypeInfo::Type powerAdjustmentCapability; + powerAdjustmentCapability = mDelegate.GetPowerAdjustmentCapability(); + + if (powerAdjustmentCapability.IsNull()) + { + ChipLogError(Zcl, "DEM: powerAdjustmentCapability IsNull"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); + return; + } + + /* PowerAdjustmentCapability is a list - so iterate through checking if the command is within one of the offers */ + for (auto pas : powerAdjustmentCapability.Value()) + { + if ((power >= pas.minPower) && (durationSec >= pas.minDuration) && (power <= pas.maxPower) && + (durationSec <= pas.maxDuration)) + { + ChipLogProgress(Zcl, "DEM: Good PowerAdjustment args"); + validArgs = true; + break; + } + } + + if (!validArgs) + { + ChipLogError(Zcl, "DEM: invalid request range"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); + return; + } + + ChipLogProgress(Zcl, "DEM: Good PowerAdjustRequest() args."); + + status = mDelegate.PowerAdjustRequest(power, durationSec); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); + if (status != Status::Success) + { + ChipLogError(Zcl, "DEM: Failed to PowerAdjustRequest() args."); + } +} + +void Instance::HandleCancelPowerAdjustRequest(HandlerContext & ctx, + const Commands::CancelPowerAdjustRequest::DecodableType & commandData) +{ + Status status = Status::Success; + ESAStateEnum esaStatus; + + /* Check that the ESA state is PowerAdjustActive */ + esaStatus = mDelegate.GetESAState(); + if (ESAStateEnum::kPowerAdjustActive != esaStatus) + { + ChipLogError(Zcl, "DEM: kPowerAdjustActive != esaStatus"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + status = mDelegate.CancelPowerAdjustRequest(); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); + if (status != Status::Success) + { + ChipLogError(Zcl, "DEM: Failed to CancelPowerAdjustRequest()"); + return; + } +} + +void Instance::HandleStartTimeAdjustRequest(HandlerContext & ctx, + const Commands::StartTimeAdjustRequest::DecodableType & commandData) +{ + Status status = Status::Success; + uint32_t earliestStartTimeEpoch = 0; + uint32_t latestEndTimeEpoch = 0; + uint32_t requestedStartTimeEpoch = commandData.requestedStartTime; + uint32_t duration; + + DataModel::Nullable forecastNullable = mDelegate.GetForecast(); + + if (forecastNullable.IsNull()) + { + ChipLogError(Zcl, "DEM: Forecast is Null"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + if (ESAStateEnum::kUserOptOut == mDelegate.GetESAState()) + { + ChipLogError(Zcl, "DEM: ESAState = kUserOptOut"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + /* Temporary variable to save calling .Value() on forecastNullable */ + auto & forecast = forecastNullable.Value(); + + /** + * If the RequestedStartTime value resulted in a time shift which is + * outside the time constraints of EarliestStartTime and + * LatestEndTime, then the command SHALL be rejected with CONSTRAINT_ERROR; + * in other failure scenarios the command SHALL be rejected with FAILURE + */ + /* earliestStartTime is optional based on the StartTimeAdjust (STA) feature AND is nullable */ + if (!(forecast.earliestStartTime.HasValue()) || !(forecast.latestEndTime.HasValue())) + { + /* These should have values, since this command requires STA feature and these are mandatory for that */ + ChipLogError(Zcl, "DEM: EarliestStartTime / LatestEndTime do not have values"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + /* Temporary variable to save keep calling .Value() on the Optional element */ + DataModel::Nullable & earliestStartTimeNullable = forecast.earliestStartTime.Value(); + /* Latest End Time is optional & cannot be null - unlike earliestStartTime! */ + latestEndTimeEpoch = forecast.latestEndTime.Value(); + + if (earliestStartTimeNullable.IsNull()) + { + System::Clock::Milliseconds64 cTMs; + CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(cTMs); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "DEM: Unable to get current time - err:%" CHIP_ERROR_FORMAT, err.Format()); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + auto unixEpoch = std::chrono::duration_cast(cTMs).count(); + uint32_t chipEpoch = 0; + if (!UnixEpochToChipEpochTime(unixEpoch, chipEpoch)) + { + ChipLogError(Zcl, "DEM: unable to convert Unix Epoch time to Matter Epoch Time"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + /* Null means - We can start immediately */ + earliestStartTimeEpoch = chipEpoch; /* NOW */ + } + else + { + earliestStartTimeEpoch = earliestStartTimeNullable.Value(); + } + + duration = forecast.endTime - forecast.startTime; // the current entire forecast duration + if (requestedStartTimeEpoch < earliestStartTimeEpoch) + { + ChipLogError(Zcl, "DEM: Bad requestedStartTime %ld, earlier than earliestStartTime %ld.", + static_cast(requestedStartTimeEpoch), + static_cast(earliestStartTimeEpoch)); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); + return; + } + + if ((requestedStartTimeEpoch + duration) > latestEndTimeEpoch) + { + ChipLogError(Zcl, "DEM: Bad requestedStartTimeEpoch + duration %ld, later than latestEndTime %ld.", + static_cast(requestedStartTimeEpoch + duration), + static_cast(latestEndTimeEpoch)); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); + return; + } + + ChipLogProgress(Zcl, "DEM: Good requestedStartTimeEpoch %ld.", static_cast(requestedStartTimeEpoch)); + status = mDelegate.StartTimeAdjustRequest(requestedStartTimeEpoch); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); + if (status != Status::Success) + { + ChipLogError(Zcl, "DEM: StartTimeAdjustRequest(%ld) FAILURE", static_cast(requestedStartTimeEpoch)); + return; + } +} + +void Instance::HandlePauseRequest(HandlerContext & ctx, const Commands::PauseRequest::DecodableType & commandData) +{ + Status status = Status::Success; + CHIP_ERROR err = CHIP_NO_ERROR; + DataModel::Nullable forecast = mDelegate.GetForecast(); + + uint32_t duration = commandData.duration; + + if (forecast.IsNull()) + { + ChipLogError(Zcl, "DEM: Forecast is Null"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + if (ESAStateEnum::kUserOptOut == mDelegate.GetESAState()) + { + ChipLogError(Zcl, "DEM: ESAState = kUserOptOut"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + /* value SHALL be between the MinPauseDuration and MaxPauseDuration indicated in the + ActiveSlotNumber index in the Slots list in the Forecast. + */ + uint16_t activeSlotNumber; + if (forecast.Value().activeSlotNumber.IsNull()) + { + ChipLogError(Zcl, "DEM: activeSlotNumber Is Null"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + activeSlotNumber = forecast.Value().activeSlotNumber.Value(); + if (activeSlotNumber >= forecast.Value().slots.size()) + { + ChipLogError(Zcl, "DEM: Bad activeSlotNumber %d , size()=%d.", activeSlotNumber, + static_cast(forecast.Value().slots.size())); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + if (!forecast.Value().slots[activeSlotNumber].slotIsPauseable) + { + ChipLogError(Zcl, "DEM: activeSlotNumber %d is NOT pauseable.", activeSlotNumber); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); + return; + } + + if ((duration < forecast.Value().slots[activeSlotNumber].minPauseDuration) && + (duration > forecast.Value().slots[activeSlotNumber].maxPauseDuration)) + { + ChipLogError(Zcl, "DEM: out of range pause duration %ld", static_cast(duration)); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::ConstraintError); + return; + } + + err = mDelegate.SetESAState(ESAStateEnum::kPaused); + if (CHIP_NO_ERROR != err) + { + ChipLogError(Zcl, "DEM: SetESAState(paused) FAILURE"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + status = mDelegate.PauseRequest(duration); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + if (status != Status::Success) + { + ChipLogError(Zcl, "DEM: mDelegate.PauseRequest(%ld) FAILURE", static_cast(duration)); + return; + } +} + +void Instance::HandleResumeRequest(HandlerContext & ctx, const Commands::ResumeRequest::DecodableType & commandData) +{ + Status status = Status::Success; + DataModel::Nullable forecast = mDelegate.GetForecast(); + + if (ESAStateEnum::kPaused != mDelegate.GetESAState()) + { + ChipLogError(Zcl, "DEM: ESAState not Paused."); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + status = mDelegate.ResumeRequest(); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + if (status != Status::Success) + { + ChipLogError(Zcl, "DEM: mDelegate.ResumeRequest() FAILURE"); + return; + } +} + +void Instance::HandleModifyForecastRequest(HandlerContext & ctx, const Commands::ModifyForecastRequest::DecodableType & commandData) +{ + Status status = Status::Success; + uint32_t forecastId = commandData.forecastId; + DataModel::Nullable forecast; + + if (ESAStateEnum::kUserOptOut == mDelegate.GetESAState()) + { + ChipLogError(Zcl, "DEM: ESAState = kUserOptOut"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + forecast = mDelegate.GetForecast(); + if (forecast.IsNull()) + { + ChipLogError(Zcl, "DEM: Forecast is Null"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + DataModel::DecodableList slotAdjustments = commandData.slotAdjustments; + status = mDelegate.ModifyForecastRequest(forecastId, slotAdjustments); + if (status != Status::Success) + { + ChipLogError(Zcl, "DEM: mDelegate.ModifyForecastRequest() FAILURE"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } +} + +void Instance::HandleRequestConstraintBasedForecast(HandlerContext & ctx, + const Commands::RequestConstraintBasedForecast::DecodableType & commandData) +{ + Status status = Status::Success; + + if (ESAStateEnum::kUserOptOut == mDelegate.GetESAState()) + { + ChipLogError(Zcl, "DEM: ESAState = kUserOptOut"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } + + status = mDelegate.RequestConstraintBasedForecast(commandData.constraints); + if (status != Status::Success) + { + ChipLogError(Zcl, "DEM: mDelegate.commandData.constraints() FAILURE"); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::Failure); + return; + } +} + +} // namespace DeviceEnergyManagement +} // namespace Clusters +} // namespace app +} // namespace chip + +void MatterDeviceEnergyManagementPluginServerInitCallback() {} diff --git a/src/app/clusters/device-energy-management-server/device-energy-management-server.h b/src/app/clusters/device-energy-management-server/device-energy-management-server.h new file mode 100644 index 00000000000000..7ab6a8aa60f306 --- /dev/null +++ b/src/app/clusters/device-energy-management-server/device-energy-management-server.h @@ -0,0 +1,214 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace DeviceEnergyManagement { + +using namespace chip::app::Clusters::DeviceEnergyManagement::Attributes; + +using chip::Protocols::InteractionModel::Status; + +class Delegate +{ +public: + virtual ~Delegate() = default; + + void SetEndpointId(EndpointId aEndpoint) { mEndpointId = aEndpoint; } + + /** + * @brief Delegate should implement a handler to begin to adjust client power + * consumption/generation to the level requested. + * + * @param power Milli-Watts the ESA SHALL use during the adjustment period. + * @param duration The duration that the ESA SHALL maintain the requested power for. + * @return Success if the adjustment is accepted; otherwise the command SHALL be rejected with appropriate error. + */ + virtual Status PowerAdjustRequest(const int64_t power, const uint32_t duration) = 0; + + /** + * @brief Delegate SHALL make the ESA end the active power adjustment session & return to normal (or idle) power levels. + * The ESA SHALL also generate an PowerAdjustEnd Event and the ESAState SHALL be restored to Online. + * + * @return It should report SUCCESS if successful and FAILURE otherwise. + */ + virtual Status CancelPowerAdjustRequest() = 0; + + /** + * @brief Delegate for the ESA SHALL update its Forecast attribute with the RequestedStartTime including a new ForecastId. + * + * If the ESA supports ForecastAdjustment, and the ESAState is not UserOptOut and the RequestedStartTime is after + * the EarliestStartTime and the resulting EndTime is before the LatestEndTime, then ESA SHALL accept the request + * to modify the Start Time. + * A client can estimate the entire Forecast sequence duration by computing the EndTime - StartTime fields from the + * Forecast attribute, and therefore avoid scheduling the start time too late. + * + * @param requestedStartTime The requested start time in UTC that the client would like the appliance to shift its power + * forecast to. + * @return Success if the StartTime in the Forecast is updated, otherwise the command SHALL be rejected with appropriate + * IM_Status. + */ + virtual Status StartTimeAdjustRequest(const uint32_t requestedStartTime) = 0; + + /** + * @brief Delegate handler for PauseRequest command + * + * If the ESA supports FA and the SlotIsPauseable field is true in the ActiveSlotNumber + * index in the Slots list, and the ESAState is not UserOptOut then the ESA SHALL allow its current + * operation to be Paused. + * + * During this state the ESA SHALL not consume or produce significant power (other than required to keep its + * basic control system operational). + * + * @param duration Duration that the ESA SHALL be paused for. + * @return Success if the ESA is paused, otherwise returns other IM_Status. + */ + virtual Status PauseRequest(const uint32_t duration) = 0; + + /** + * @brief Delegate handler for ResumeRequest command + * + * If the ESA supports FA and it is currently Paused then the ESA SHALL resume its operation. + * The ESA SHALL also generate a Resumed Event and the ESAState SHALL be updated accordingly to + * reflect its current state. + * + * @return Success if the ESA is resumed, otherwise returns other IM_Status. + */ + virtual Status ResumeRequest() = 0; + + /** + * @brief Delegate handler for ModifyForecastRequest + * + * If the ESA supports FA, and the ESAState is not UserOptOut it SHALL attempt to adjust its power forecast. + * This allows a one or more modifications in a single command by sending a list of modifications (one for each 'slot'). + * Attempts to modify slots which have already past, SHALL result in the entire command being rejected. + * If the ESA accepts the requested Forecast then it SHALL update its Forecast attribute (incrementing its ForecastId) + * and run the revised Forecast as its new intended operation. + * + * @param forecastId Indicates the ESA ForecastId that is to be modified. + * @param slotAdjustments List of adjustments to be applied to the ESA, corresponding to the expected ESA forecastId. + * @return Success if the entire list of SlotAdjustmentStruct are accepted, otherwise the command + * SHALL be rejected returning other IM_Status. + */ + virtual Status ModifyForecastRequest(const uint32_t forecastId, + const DataModel::DecodableList & slotAdjustments) = 0; + + /** + * @brief Delegate handler for RequestConstraintBasedForecast + * + * The ESA SHALL inspect the requested power limits to ensure that there are no overlapping elements. The ESA + * manufacturer may also reject the request if it could cause the user’s preferences to be breached (e.g. may + * cause the home to be too hot or too cold, or a battery to be insufficiently charged). + * If the ESA can meet the requested power limits, it SHALL regenerate a new Power Forecast with a new ForecastId. + * + * @param constraints Sequence of turn up/down power requests that the ESA is being asked to constrain its operation within. + * @return Success if successful, otherwise the command SHALL be rejected returning other IM_Status. + */ + virtual Status + RequestConstraintBasedForecast(const DataModel::DecodableList & constraints) = 0; + + // ------------------------------------------------------------------ + // Get attribute methods + virtual ESATypeEnum GetESAType() = 0; + virtual bool GetESACanGenerate() = 0; + virtual ESAStateEnum GetESAState() = 0; + virtual int64_t GetAbsMinPower() = 0; + virtual int64_t GetAbsMaxPower() = 0; + virtual PowerAdjustmentCapability::TypeInfo::Type GetPowerAdjustmentCapability() = 0; + virtual DataModel::Nullable GetForecast() = 0; + + // ------------------------------------------------------------------ + // Set attribute methods + virtual CHIP_ERROR SetESAType(ESATypeEnum) = 0; + virtual CHIP_ERROR SetESACanGenerate(bool) = 0; + virtual CHIP_ERROR SetESAState(ESAStateEnum) = 0; + virtual CHIP_ERROR SetAbsMinPower(int64_t) = 0; + virtual CHIP_ERROR SetAbsMaxPower(int64_t) = 0; + virtual CHIP_ERROR SetPowerAdjustmentCapability(PowerAdjustmentCapability::TypeInfo::Type) = 0; + virtual CHIP_ERROR SetForecast(DataModel::Nullable) = 0; + +protected: + EndpointId mEndpointId = 0; +}; + +enum class OptionalCommands : uint32_t +{ + kSupportsModifyForecastRequest = 0x1, + kSupportsRequestConstraintBasedForecast = 0x2 +}; + +class Instance : public AttributeAccessInterface, public CommandHandlerInterface +{ +public: + Instance(EndpointId aEndpointId, Delegate & aDelegate, Feature aFeature, OptionalCommands aOptionalCmds) : + AttributeAccessInterface(MakeOptional(aEndpointId), Id), CommandHandlerInterface(MakeOptional(aEndpointId), Id), + mDelegate(aDelegate), mFeature(aFeature), mOptionalCmds(aOptionalCmds) + { + /* set the base class delegates endpointId */ + mDelegate.SetEndpointId(aEndpointId); + } + + ~Instance() { Shutdown(); } + + CHIP_ERROR Init(); + void Shutdown(); + + bool HasFeature(Feature aFeature) const; + bool SupportsOptCmd(OptionalCommands aOptionalCmds) const; + +private: + Delegate & mDelegate; + BitMask mFeature; + BitMask mOptionalCmds; + + // AttributeAccessInterface + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + // NOTE there are no writable attributes + + // CommandHandlerInterface + void InvokeCommand(HandlerContext & handlerContext) override; + CHIP_ERROR EnumerateAcceptedCommands(const ConcreteClusterPath & cluster, CommandIdCallback callback, void * context) override; + + void HandlePowerAdjustRequest(HandlerContext & ctx, const Commands::PowerAdjustRequest::DecodableType & commandData); + void HandleCancelPowerAdjustRequest(HandlerContext & ctx, + const Commands::CancelPowerAdjustRequest::DecodableType & commandData); + void HandleStartTimeAdjustRequest(HandlerContext & ctx, const Commands::StartTimeAdjustRequest::DecodableType & commandData); + void HandlePauseRequest(HandlerContext & ctx, const Commands::PauseRequest::DecodableType & commandData); + void HandleResumeRequest(HandlerContext & ctx, const Commands::ResumeRequest::DecodableType & commandData); + void HandleModifyForecastRequest(HandlerContext & ctx, const Commands::ModifyForecastRequest::DecodableType & commandData); + void HandleRequestConstraintBasedForecast(HandlerContext & ctx, + const Commands::RequestConstraintBasedForecast::DecodableType & commandData); +}; + +} // namespace DeviceEnergyManagement +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.cpp b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.cpp new file mode 100644 index 00000000000000..78fb87085f3b10 --- /dev/null +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.cpp @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "EnergyEvseTestEventTriggerDelegate.h" + +using namespace chip::app::Clusters::EnergyEvse; + +namespace chip { + +bool EnergyEvseTestEventTriggerDelegate::DoesEnableKeyMatch(const ByteSpan & enableKey) const +{ + return !mEnableKey.empty() && mEnableKey.data_equal(enableKey); +} + +CHIP_ERROR EnergyEvseTestEventTriggerDelegate::HandleEventTrigger(uint64_t eventTrigger) +{ + if (HandleEnergyEvseTestEventTrigger(eventTrigger)) + { + return CHIP_NO_ERROR; + } + if (mOtherDelegate != nullptr) + { + return mOtherDelegate->HandleEventTrigger(eventTrigger); + } + return CHIP_ERROR_INVALID_ARGUMENT; +} + +} // namespace chip diff --git a/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h new file mode 100644 index 00000000000000..3c1dff4f5e7f81 --- /dev/null +++ b/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h @@ -0,0 +1,98 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +namespace chip { + +/* + * These Test EventTrigger values are specified in the TC_EEVSE test plan + * and are defined conditions used in test events. + * + * They are sent along with the enableKey (manufacturer defined secret) + * in the General Diagnostic cluster TestEventTrigger command + */ +enum class EnergyEvseTrigger : uint64_t +{ + // Scenarios + // Basic Functionality Test Event | Simulate installation with _{A_CIRCUIT_CAPACITY}_=32A and + // _{A_USER_MAXIMUM_CHARGE_CURRENT}_=32A + kBasicFunctionality = 0x0099000000000000, + // Basic Functionality Test Event Clear | End simulation of installation + kBasicFunctionalityClear = 0x0099000000000001, + // EV Plugged-in Test Event | Simulate plugging the EV into the EVSE using a cable of 63A capacity + kEVPluggedIn = 0x0099000000000002, + // EV Plugged-in Test Event Clear | Simulate unplugging the EV + kEVPluggedInClear = 0x0099000000000003, + // EV Charge Demand Test Event | Simulate the EV presenting charge demand to the EVSE + kEVChargeDemand = 0x0099000000000004, + // EV Charge Demand Test Event Clear | Simulate the EV becoming fully charged + kEVChargeDemandClear = 0x0099000000000005, + // EVSE has a GroundFault fault + kEVSEGroundFault = 0x0099000000000010, + // EVSE has a OverTemperature fault + kEVSEOverTemperatureFault = 0x0099000000000011, + // EVSE faults have cleared + kEVSEFaultClear = 0x0099000000000012, + // EVSE Diagnostics Complete | Simulate diagnostics have been completed and return to normal + kEVSEDiagnosticsComplete = 0x0099000000000020, +}; + +class EnergyEvseTestEventTriggerDelegate : public TestEventTriggerDelegate +{ +public: + /** + * This class expects the enableKey ByteSpan to be valid forever. + * Typically this feature is only enabled in certification testing + * and uses a static secret key in the device for testing (e.g. in factory data) + */ + explicit EnergyEvseTestEventTriggerDelegate(const ByteSpan & enableKey, TestEventTriggerDelegate * otherDelegate) : + mEnableKey(enableKey), mOtherDelegate(otherDelegate) + {} + + /* This function returns True if the enableKey received in the TestEventTrigger command + * matches the value passed into the constructor. + */ + bool DoesEnableKeyMatch(const ByteSpan & enableKey) const override; + + /** This function must return True if the eventTrigger is recognised and handled + * It must return False to allow a higher level TestEvent handler to check other + * clusters that may handle it. + */ + CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override; + +private: + ByteSpan mEnableKey; + TestEventTriggerDelegate * mOtherDelegate; +}; + +} // namespace chip + +/** + * @brief User handler for handling the test event trigger + * + * @note If TestEventTrigger is enabled, it needs to be implemented in the app + * + * @param eventTrigger Event trigger to handle + * + * @retval true on success + * @retval false if error happened + */ +bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger); 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 60f9e24be0f962..019aead77c24a8 100644 --- a/src/app/clusters/energy-evse-server/energy-evse-server.cpp +++ b/src/app/clusters/energy-evse-server/energy-evse-server.cpp @@ -23,6 +23,7 @@ using namespace chip; using namespace chip::app; +using namespace chip::app::DataModel; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; using namespace chip::app::Clusters::EnergyEvse::Attributes; @@ -125,6 +126,10 @@ CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValu return aEncoder.Encode(mDelegate.GetSessionEnergyCharged()); case SessionEnergyDischarged::Id: return aEncoder.Encode(mDelegate.GetSessionEnergyDischarged()); + + /* FeatureMap - is held locally */ + case FeatureMap::Id: + return aEncoder.Encode(mFeature); } /* Allow all other unhandled attributes to fall through to Ember */ return CHIP_NO_ERROR; @@ -166,7 +171,7 @@ CHIP_ERROR Instance::Write(const ConcreteDataAttributePath & aPath, AttributeVal } uint16_t newValue; ReturnErrorOnFailure(aDecoder.Decode(newValue)); - ReturnErrorOnFailure(mDelegate.SetApproximateEVEfficiency(newValue)); + ReturnErrorOnFailure(mDelegate.SetApproximateEVEfficiency(MakeNullable(newValue))); return CHIP_NO_ERROR; } 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 5df2f0149eb1ed..a194551607c6f4 100644 --- a/src/app/clusters/energy-evse-server/energy-evse-server.h +++ b/src/app/clusters/energy-evse-server/energy-evse-server.h @@ -48,6 +48,7 @@ class Delegate virtual ~Delegate() = default; void SetEndpointId(EndpointId aEndpoint) { mEndpointId = aEndpoint; } + EndpointId GetEndpointId() { return mEndpointId; } /** * @brief Delegate should implement a handler to disable the EVSE. @@ -116,9 +117,9 @@ class Delegate // ------------------------------------------------------------------ // Set attribute methods - virtual CHIP_ERROR SetUserMaximumChargeCurrent(int64_t aNewValue) = 0; - virtual CHIP_ERROR SetRandomizationDelayWindow(uint32_t aNewValue) = 0; - virtual CHIP_ERROR SetApproximateEVEfficiency(uint16_t aNewValue) = 0; + virtual CHIP_ERROR SetUserMaximumChargeCurrent(int64_t aNewValue) = 0; + virtual CHIP_ERROR SetRandomizationDelayWindow(uint32_t aNewValue) = 0; + virtual CHIP_ERROR SetApproximateEVEfficiency(DataModel::Nullable aNewValue) = 0; protected: EndpointId mEndpointId = 0; diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json index 707947cc61558a..a34fdd5de44e79 100644 --- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json +++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json @@ -178,7 +178,42 @@ ], "Bridged Device Basic Information": ["ProductAppearance"], "Descriptor": ["ClusterRevision"], - "Device Energy Management": ["Forecast"], + "Device Energy Management": [ + "ESAType", + "ESACanGenerate", + "ESAState", + "AbsMinPower", + "AbsMaxPower", + "PowerAdjustmentCapability", + "Forecast", + "FeatureMap" + ], + "Energy EVSE": [ + "State", + "SupplyState", + "FaultState", + "ChargingEnabledUntil", + "DischargingEnabledUntil", + "CircuitCapacity", + "MinimumChargeCurrent", + "MaximumChargeCurrent", + "MaximumDischargeCurrent", + "UserMaximumChargeCurrent", + "RandomizationDelayWindow", + "NextChargeStartTime", + "NextChargeTargetTime", + "NextChargeRequiredEnergy", + "NextChargeTargetSoC", + "ApproximateEVEfficiency", + "StateOfCharge", + "BatteryCapacity", + "VehicleID", + "SessionID", + "SessionDuration", + "SessionEnergyCharged", + "SessionEnergyDischarged", + "FeatureMap" + ], "Ethernet Network Diagnostics": [ "PHYRate", "FullDuplex", diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json index 4898e0b2e6d3b4..0f4f2772f14101 100644 --- a/src/app/zap-templates/zcl/zcl.json +++ b/src/app/zap-templates/zcl/zcl.json @@ -176,7 +176,42 @@ ], "Bridged Device Basic Information": ["ProductAppearance"], "Descriptor": ["ClusterRevision"], - "Device Energy Management": ["Forecast"], + "Device Energy Management": [ + "ESAType", + "ESACanGenerate", + "ESAState", + "AbsMinPower", + "AbsMaxPower", + "PowerAdjustmentCapability", + "Forecast", + "FeatureMap" + ], + "Energy EVSE": [ + "State", + "SupplyState", + "FaultState", + "ChargingEnabledUntil", + "DischargingEnabledUntil", + "CircuitCapacity", + "MinimumChargeCurrent", + "MaximumChargeCurrent", + "MaximumDischargeCurrent", + "UserMaximumChargeCurrent", + "RandomizationDelayWindow", + "NextChargeStartTime", + "NextChargeTargetTime", + "NextChargeRequiredEnergy", + "NextChargeTargetSoC", + "ApproximateEVEfficiency", + "StateOfCharge", + "BatteryCapacity", + "VehicleID", + "SessionID", + "SessionDuration", + "SessionEnergyCharged", + "SessionEnergyDischarged", + "FeatureMap" + ], "Ethernet Network Diagnostics": [ "PHYRate", "FullDuplex", diff --git a/src/python_testing/TC_DEM_2_1.py b/src/python_testing/TC_DEM_2_1.py new file mode 100644 index 00000000000000..3f2f5e73d017aa --- /dev/null +++ b/src/python_testing/TC_DEM_2_1.py @@ -0,0 +1,41 @@ + +import logging + +import chip.clusters as Clusters +from chip.interaction_model import Status +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main +from mobly import asserts + +# List of attributes tests in the following format: ['Attribute name',read/write, Value to Write, ExpectedValue, Test step] +Attribute_names = [ + ['ESAType', 'r', None, None, ('print_step', (2, 'TH reads from DUT the ESAType attribute'))], + ['ESACanGenerate', 'r', None, None, ('print_step', (3, 'Th reads from the DUT the ESACanGenerate attribute'))], + ['ESAState', 'r', None, None, ('print_step', (4, 'Th reads from the DUT the ESAState attribute'))], + ['AbsMinPower', 'r', None, None, ('print_step', (5, 'Th reads from the DUT the AbsMinPower attribute'))], + ['AbsMaxPower', 'r', None, None, ('print_step', (6, 'Th reads from the DUT the AbsMaxPower attribute'))], + ['PowerAdjustmentCapability', 'r', None, None, + ('print_step', (7, 'Th reads from the DUT the PowerAdjustmentCapability attribute'))], + ['Forecast', 'r', None, None, ('print_step', (8, 'Th reads from the DUT the Forecast attribute'))], + +] + + +class DemTest(MatterBaseTest): + + async def read_dem_attribute_exepct_success(self, endpoint, attribute): + full_attr = getattr(Clusters.DeviceEnergyManagement.Attributes, attribute) + cluster = Clusters.Objects.DeviceEnergyManagement + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + @async_test_body + async def test_TC_DEM_2_1(self): + for attribute_name, rw, value_to_write, expected_value, step in Attribute_names: + function, args = step + getattr(self, function)(*args) + + await self.read_dem_attribute_exepct_success(endpoint=1, attribute=attribute_name) + #asserts.assert_not_equal(attribute, None, f"{attribute_name} should return a value but instead returns None") + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_DEM_2_2.py b/src/python_testing/TC_DEM_2_2.py new file mode 100644 index 00000000000000..9460c8ad400fc7 --- /dev/null +++ b/src/python_testing/TC_DEM_2_2.py @@ -0,0 +1,131 @@ +import datetime +import logging +import time + +import chip.clusters as Clusters +import pytz +from chip.interaction_model import Status +from matter_testing_support import MatterBaseTest, async_test_body, default_matter_test_main +from mobly import asserts + +logger = logging.getLogger(__name__) + + +class TC_DEM_2_2(MatterBaseTest): + + async def read_dem_attribute_exepct_success(self, endpoint, attribute): + full_attr = getattr(Clusters.DeviceEnergyManagement.Attributes, attribute) + cluster = Clusters.Objects.DeviceEnergyManagement + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + async def send_power_adj_request_command(self, endpoint: int = 0, power: int = None, duration: int = 0 timedRequestTimeoutMs: int = 60000, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.DeviceEnergyMangement.Commands.PowerAdjustRequest(power=power, duration=duration), endpoint=1, timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + pass + + async def send_cncl_power_adj_request_command(self, endpoint: int = 0, timedRequestTimeoutMs: int = 60000, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.DeviceEnergyMangement.Commands.CanceelPowerAdjustRequest(), endpoint=1, timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + pass + + @async_test_body + async def test_TC_DEM_2_2(self): + + # Part 1 + self.print_step(1, "Commissioning, already done") + + # Part 2 TODO Requires TestEventTriggers + + # Part 3 TODO Requires TestEventTriggers + """These tests will not work without the test event triggers.""" + # self.print_step('3a', "TH reads from the DUT ESAType") + # esa_type = await self.read_dem_attribute_exepct_success(endpoint = 1, attribute='ESAType') + # asserts.assert_equal(esa_type, 5, f'ESAType should be 5, but is actually {esa_type}') + + # self.print_step('3b', "TH reads from the DUT ESACanGenerate") + # esa_can_generate = await self.read_dem_attribute_exepct_success(endpoint = 1, attribute='ESACanGenerate') + # asserts.assert_equal(esa_can_generate, True, f'ESACanGenerate should be 5, but is actually {esa_can_generate}') + + # self.print_step('3c', "TH reads from the DUT ESAState") + # esa_state = await self.read_dem_attribute_exepct_success(endpoint = 1, attribute='ESAState') + # asserts.assert_equal(esa_state, 1, f'ESAState should be 1, but is actually {esa_state}') + + # self.print_step('3d', "TH reads from the DUT AbsMinPower") + # abs_min_power = await self.read_dem_attribute_exepct_success(endpoint = 1, attribute='AbsMinPower') + # asserts.assert_equal(abs_min_power, -2000000, f'AbsMinPower should be -2000000, but is actually {abs_min_power}') + + # self.print_step('3e', "TH reads from the DUT AbsMaxPower") + # abs_max_power = await self.read_dem_attribute_exepct_success(endpoint = 1, attribute='AbsMaxPower') + # asserts.assert_equal(abs_max_power, 3000000, f'AbsMaxPowershould be 3000000, but is actually {abs_max_power}') + + # self.print_step('3f', "TH reads from the DUT PowerAdjustmentCapability") + # power_adj_cap = await self.read_dem_attribute_exepct_success(endpoint = 1, attribute='PowerAdjustmentCapability') + # asserts.assert_equal(power_adj_cap, 3000000, f'PowerAdjustmentCapability should be 3000000, but is actually {abs_max_power}') + + # Part 4 + self.print_step(4, "TH sends command PowerAdjustRequest with power= -2000000 and duration =120s") + await self.send_power_adj_request_command(endpoint=1, power=-2000000, duration=120) + + self.print_step('4a', "TH reads from the DUT ESAState") + esa_state = await self.read_dem_attribute_exepct_success(endpoint=1, attribute='ESAState') + asserts.assert_equal(esa_state, 4, f'ESAState should be 4, but is actually {esa_state}') + + # Part 5 + self.print_step(5, "Wait 2 minutes") + time.sleep(120) + + # Part 6 + self.print_step(6, "TH sends command PowerAdjustRequest with power= 3000000 and duration =120s") + await self.send_power_adj_request_command(endpoint=1, power=-2000000, duration=120) + + # Part 7 + self.print_step(7, "Wait 1 minute") + time.sleep(60) + + # Part 8 + self.print_step(8, "TH sends command CancelPowerAdjustRequest") + await self.send_cncl_power_adj_request_command(endpoint=1) + + # Part 9 + self.print_step(9, "TH sends command CancelPowerAdjustRequest") + cncl_power_adj = await self.send_cncl_power_adj_request_command(endpoint=1) + asserts.assert_equal(cncl_power_adj, Status.Failure, f'CancelPowerAdjustment command should fail, but does not') + + # Part 10 + self.print_step(10, "TH sends command PowerAdjustRequest with power = 5000000 and duration = 120") + power_adj = await self.send_power_adj_request_command(endpoint=1, power=5000000, duration=120) + asserts.assert_equal(power_adj, Status.Failure, f'PowerAdjustment command should fail, but does not') + + # Part 11 + self.print_step(11, "TH sends command PowerAdjustRequest with power = 3000000 and duration = 10000") + power_adj = await self.send_power_adj_request_command(endpoint=1, power=3000000, duration=10000) + asserts.assert_equal(power_adj, Status.Failure, f'PowerAdjustment command should fail, but does not') + + # Part 12 + self.print_step(12, "TH sends command PowerAdjustRequest with power = 3000000 and duration = 120") + power_adj = await self.send_power_adj_request_command(endpoint=1, power=3000000, duration=120) + asserts.assert_equal(power_adj, Status.Success, f'PowerAdjustment command should Succeed but does not') + + # Part 13 + self.print_step(5, "Wait 1 minute") + time.sleep(60) + + # Part 14 + self.print_step(12, "TH sends command PowerAdjustRequest with power = 3000000 and duration = 120") + power_adj = await self.send_power_adj_request_command(endpoint=1, power=3000000, duration=120) + + # Part 15 TODO RequiresTestEventTriggers + + # Part 16 TODO Requires TestEventTriggers + + # Part 17 TODO Requires TestEventTriggers + + # Part 5 TODO Requires TestEventTriggers +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EEVSE_2_2.py b/src/python_testing/TC_EEVSE_2_2.py new file mode 100644 index 00000000000000..8ebc78a22e94cd --- /dev/null +++ b/src/python_testing/TC_EEVSE_2_2.py @@ -0,0 +1,319 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import logging +import time +from datetime import datetime, timedelta, timezone + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts +from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback + +logger = logging.getLogger(__name__) + + +class TC_EEVSE_2_2(MatterBaseTest, EEVSEBaseTestHelper): + + def desc_TC_EEVSE_2_2(self) -> str: + """Returns a description of this test""" + return "5.1.3. [TC-EEVSE-2.2] Primary functionality with DUT as Server" + + def pics_TC_EEVSE_2_2(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + # In this case - there is no feature flags needed to run this test case + return None + + def steps_TC_EEVSE_2_2(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event"), + TestStep("3a", "After a few seconds TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)"), + 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 EV Plugged-in Test Event. Verify Event EEVSE.S.E00(EVConnected) sent"), + TestStep("4a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), + TestStep("4b", "TH reads from the DUT the SessionID attribute. Value is noted for later"), + TestStep("5", "TH sends command EnableCharging with ChargingEnabledUntil=2 minutes in the future, minimumChargeCurrent=6000, maximumChargeCurrent=60000"), + TestStep("6", "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 Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), + TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x3 (PluggedInCharging)"), + TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x1 (ChargingEnabled)"), + TestStep("6c", "TH reads from the DUT the ChargingEnabledUntil attribute. Verify value is the commanded value"), + TestStep("6d", "TH reads from the DUT the MinimumChargeCurrent attribute. Verify value is the commanded value (6000)"), + TestStep("6e", "TH reads from the DUT the MaximumChargeCurrent attribute. Verify value is the min(command value (60000), CircuitCapacity)"), + TestStep("7", "Wait 2 minutes. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvseStopped"), + TestStep("7a", "TH reads from the DUT the State attribute. Verify value is 0x02 (PluggedInDemand)"), + TestStep("7b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("8", "TH sends command EnableCharging with ChargingEnabledUntil=NULL, minimumChargeCurrent = 6000, maximumChargeCurrent=12000"), + TestStep("8a", "TH reads from the DUT the State attribute. Verify value is 0x03 (PluggedInCharging)"), + TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 1 (ChargingEnabled)"), + TestStep("8c", "TH reads from the DUT the ChargingEnabledUntil attribute. Verify value is the commanded value (NULL)"), + TestStep("8d", "TH reads from the DUT the MinimumChargeCurrent attribute. Verify value is the commanded value (6000)"), + TestStep("8e", "TH reads from the DUT the MaximumChargeCurrent attribute. Verify value is the MIN(command value (60000), CircuitCapacity)"), + TestStep("9", "If the optional attribute is supported TH writes to the DUT UserMaximumChargeCurrent=6000"), + TestStep("9a", "After a few seconds TH reads from the DUT the MaximumChargeCurrent. Verify value is UserMaximumChargeCurrent value (6000)"), + TestStep("10", "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 Charge Demand Test Event Clear. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped"), + TestStep("10a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), + TestStep("11", "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 Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), + TestStep("11a", "TH reads from the DUT the State attribute. Verify value is 0x03 (PluggedInCharging)"), + TestStep("12", "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 Charge Demand Test Event Clear. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvStopped"), + TestStep("12a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), + TestStep("13", "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 Event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("13a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("13b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x01 (ChargingEnabled)"), + TestStep("13c", "TH reads from the DUT the SessionID attribute. Verify value is the same value noted in 4b"), + TestStep("13d", "TH reads from the DUT the SessionDuration attribute. Verify value is greater than 120 (and match the time taken for the tests from step 4 to step 13)"), + TestStep("14", "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 Event EEVSE.S.E00(EVConnected) sent"), + TestStep("14a", "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 Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), + TestStep("14b", "TH reads from the DUT the SessionID attribute. Verify value is 1 more than the value noted in 4b"), + TestStep("15", "TH sends command Disable. Verify Event EEVSE.S.E03(EnergyTransferStopped) sent with reason EvseStopped"), + TestStep("15a", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("16", "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 Charge Demand Test Event Clear."), + TestStep("17", "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 Event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("18", "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."), + ] + + return steps + + @async_test_body + async def test_TC_EEVSE_2_2(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() + + # After a few seconds... + time.sleep(1) + + self.step("3a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("3b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("3c") + await self.check_evse_attribute("FaultState", Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError) + + self.step("4") + await self.send_test_event_trigger_pluggedin() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVConnected) + + self.step("4a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) + + self.step("4b") + # Save Session ID for later and check it against the value in the event + session_id = await self.read_evse_attribute_expect_success(attribute="SessionID") + self.validate_ev_connected_event(event_data, session_id) + + self.step("5") + charging_duration = 5 # TODO test plan spec says 120s - reduced for now + min_charge_current = 6000 + max_charge_current = 60000 + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging + # get epoch time for ChargeUntil variable (2 minutes from now) + utc_time_charging_end = datetime.now(tz=timezone.utc) + timedelta(seconds=charging_duration) + + # Matter epoch is 0 hours, 0 minutes, 0 seconds on Jan 1, 2000 UTC + epoch_time = int((utc_time_charging_end - datetime(2000, 1, 1, 0, 0, 0, 0, timezone.utc)).total_seconds()) + await self.send_enable_charge_command(endpoint=1, charge_until=epoch_time, min_charge=min_charge_current, max_charge=max_charge_current) + + self.step("6") + await self.send_test_event_trigger_charge_demand() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + + self.step("6a") + await self.check_evse_attribute("State", expected_state) + + self.step("6b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("6c") + await self.check_evse_attribute("ChargingEnabledUntil", epoch_time) + + self.step("6d") + await self.check_evse_attribute("MinimumChargeCurrent", min_charge_current) + + self.step("6e") + circuit_capacity = await self.read_evse_attribute_expect_success(attribute="CircuitCapacity") + expected_max_charge = min(max_charge_current, circuit_capacity) + await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) + + self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) + + self.step("7") + # Sleep for the charging duration plus a couple of seconds to check it has stopped + time.sleep(charging_duration + 2) + # check EnergyTransferredStoped (EvseStopped) + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVSEStopped + self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) + + self.step("7a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInDemand) + + self.step("7b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("8") + charge_until = NullValue + min_charge_current = 6000 + max_charge_current = 12000 + + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + + self.step("8a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("8b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("8c") + await self.check_evse_attribute("ChargingEnabledUntil", charge_until) + + self.step("8d") + await self.check_evse_attribute("MinimumChargeCurrent", min_charge_current) + + self.step("8e") + circuit_capacity = await self.read_evse_attribute_expect_success(attribute="CircuitCapacity") + expected_max_charge = min(max_charge_current, circuit_capacity) + await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) + + # from step 8 above - validate event + self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) + + self.step("9") + # This will only work if the optional UserMaximumChargeCurrent attribute is supported + supported_attributes = await self.get_supported_energy_evse_attributes() + if Clusters.EnergyEvse.Attributes.UserMaximumChargeCurrent.attribute_id in supported_attributes: + logging.info("UserMaximumChargeCurrent is supported...") + user_max_charge_current = 6000 + await self.write_user_max_charge(1, user_max_charge_current) + + self.step("9a") + time.sleep(3) + + expected_max_charge = min(user_max_charge_current, circuit_capacity) + await self.check_evse_attribute("MaximumChargeCurrent", expected_max_charge) + else: + logging.info("UserMaximumChargeCurrent is NOT supported... skipping.") + + self.step("10") + await self.send_test_event_trigger_charge_demand_clear() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVStopped + self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) + + self.step("10a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) + + self.step("11") + await self.send_test_event_trigger_charge_demand() + # Check we get EnergyTransferStarted again + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) + + self.step("11a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("12") + await self.send_test_event_trigger_charge_demand_clear() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVStopped + self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) + + self.step("12a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) + + self.step("13") + await self.send_test_event_trigger_pluggedin_clear() + event_data = events_callback.WaitForEventReport(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("13a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("13b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("13c") + await self.check_evse_attribute("SessionID", session_id) + + self.step("13d") + session_duration = await self.read_evse_attribute_expect_success(attribute="SessionDuration") + asserts.assert_greater_equal(session_duration, charging_duration, + f"Unexpected 'SessionDuration' value - expected >= {charging_duration}, was {session_duration}") + + self.step("14") + await self.send_test_event_trigger_pluggedin() + # New plug in means session ID should increase by 1 + session_id = session_id + 1 + + # Check we get a new EVConnected event with updated session ID + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVConnected) + self.validate_ev_connected_event(event_data, session_id) + + self.step("14a") + await self.send_test_event_trigger_charge_demand() + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging # This is the value at the event time + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + self.validate_energy_transfer_started_event(event_data, session_id, expected_state, expected_max_charge) + + self.step("14b") + await self.check_evse_attribute("SessionID", session_id) + + self.step("15") + await self.send_disable_command() + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging # This is the value prior to stopping + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + expected_reason = Clusters.EnergyEvse.Enums.EnergyTransferStoppedReasonEnum.kEVSEStopped + self.validate_energy_transfer_stopped_event(event_data, session_id, expected_state, expected_reason) + + self.step("15a") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("16") + await self.send_test_event_trigger_charge_demand_clear() + + self.step("17") + await self.send_test_event_trigger_pluggedin_clear() + event_data = events_callback.WaitForEventReport(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("18") + await self.send_test_event_trigger_basic_clear() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EEVSE_2_4.py b/src/python_testing/TC_EEVSE_2_4.py new file mode 100644 index 00000000000000..9379e23332e813 --- /dev/null +++ b/src/python_testing/TC_EEVSE_2_4.py @@ -0,0 +1,185 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import logging +import time + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback + +logger = logging.getLogger(__name__) + + +class TC_EEVSE_2_4(MatterBaseTest, EEVSEBaseTestHelper): + + def desc_TC_EEVSE_2_4(self) -> str: + """Returns a description of this test""" + return "5.1.XXX. [TC-EEVSE-2.4] Fault test functionality with DUT as Server" + + def pics_TC_EEVSE_2_4(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + # In this case - there is no feature flags needed to run this test case + return None + + def steps_TC_EEVSE_2_4(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event"), + TestStep("3a", "After a few seconds TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)"), + 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 EV Plugged-in Test Event. Verify Event EEVSE.S.E00(EVConnected) sent"), + TestStep("4a", "TH reads from the DUT the State attribute. Verify value is 0x01 (PluggedInNoDemand)"), + TestStep("4b", "TH reads from the DUT the SessionID attribute. Value is saved for later"), + TestStep("5", "TH sends command EnableCharging with ChargingEnabledUntil=Null, minimumChargeCurrent=6000, maximumChargeCurrent=60000"), + TestStep("6", "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 Charge Demand Test Event. Verify Event EEVSE.S.E02(EnergyTransferStarted) sent."), + TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x3 (PluggedInCharging)"), + TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x1 (ChargingEnabled)"), + TestStep("7", "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 Ground Fault Test Event. Verify Event EEVSE.S.E04(Fault) sent with SessionID matching value in step 4b, FaultStatePreviousFaultState = 0x00 (NoError), FaultStateCurrentFaultState = 0x07 (GroundFault)"), + TestStep("7a", "TH reads from the DUT the State attribute. Verify value is 0x6 (Fault)"), + TestStep("7b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x4 (DisabledError)"), + TestStep("8", "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 Over Temperature Fault Test Event. Verify Event EEVSE.S.E04(Fault) sent with SessionID matching value in step 4b, FaultStatePreviousFaultState = 0x07 (GroundFault), FaultStateCurrentFaultState = 0x0F (OverTemperature)"), + TestStep("8a", "TH reads from the DUT the State attribute. Verify value is 0x6 (Fault)"), + TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x4 (DisabledError)"), + TestStep("9", "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 Fault Test Event Clear. Verify Event EEVSE.S.E04(Fault) sent with SessionID matching value in step 4b, FaultStatePreviousFaultState = 0x0F (OverTemperature), FaultStateCurrentFaultState = 0x00 (NoError)"), + TestStep("9a", "TH reads from the DUT the State attribute. Verify value is 0x3 (PluggedInCharging)"), + TestStep("9b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x1 (ChargingEnabled)"), + TestStep("10", "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 Charge Demand Test Event Clear."), + TestStep("11", "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 Event EEVSE.S.E01(EVNotDetected) sent"), + TestStep("12", "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."), + ] + + return steps + + @async_test_body + async def test_TC_EEVSE_2_4(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() + + # After a few seconds... + time.sleep(3) + + self.step("3a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("3b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("3c") + await self.check_evse_attribute("FaultState", Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError) + + self.step("4") + await self.send_test_event_trigger_pluggedin() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EVConnected) + + self.step("4a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInNoDemand) + + self.step("4b") + # Save Session ID for later and check it against the value in the event + session_id = await self.read_evse_attribute_expect_success(attribute="SessionID") + self.validate_ev_connected_event(event_data, session_id) + + self.step("5") + charge_until = NullValue + min_charge_current = 6000 + max_charge_current = 60000 + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + + self.step("6") + await self.send_test_event_trigger_charge_demand() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStarted) + + self.step("6a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("6b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("7") + await self.send_test_event_trigger_evse_ground_fault() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.Fault) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging + previous_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError + current_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kGroundFault + self.validate_evse_fault_event(event_data, session_id, expected_state, previous_fault, current_fault) + + self.step("7a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kFault) + + self.step("7b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledError) + + self.step("8") + await self.send_test_event_trigger_evse_over_temperature_fault() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.Fault) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kFault + previous_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kGroundFault + current_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kOverTemperature + self.validate_evse_fault_event(event_data, session_id, expected_state, previous_fault, current_fault) + + self.step("8a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kFault) + + self.step("8b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledError) + + self.step("9") + await self.send_test_event_trigger_evse_fault_clear() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.Fault) + expected_state = Clusters.EnergyEvse.Enums.StateEnum.kFault + previous_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kOverTemperature + current_fault = Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError + self.validate_evse_fault_event(event_data, session_id, expected_state, previous_fault, current_fault) + + self.step("9a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kPluggedInCharging) + + self.step("9b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("10") + await self.send_test_event_trigger_charge_demand_clear() + event_data = events_callback.WaitForEventReport(Clusters.EnergyEvse.Events.EnergyTransferStopped) + + self.step("11") + await self.send_test_event_trigger_pluggedin_clear() + event_data = events_callback.WaitForEventReport(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("12") + await self.send_test_event_trigger_basic_clear() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_EEVSE_2_5.py b/src/python_testing/TC_EEVSE_2_5.py new file mode 100644 index 00000000000000..34ca0151935f93 --- /dev/null +++ b/src/python_testing/TC_EEVSE_2_5.py @@ -0,0 +1,137 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import logging + +import chip.clusters as Clusters +from chip.clusters.Types import NullValue +from chip.interaction_model import Status +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from TC_EEVSE_Utils import EEVSEBaseTestHelper, EventChangeCallback + +logger = logging.getLogger(__name__) + + +class TC_EEVSE_2_5(MatterBaseTest, EEVSEBaseTestHelper): + + def desc_TC_EEVSE_2_5(self) -> str: + """Returns a description of this test""" + return "5.1.XXX. [TC-EEVSE-2.4] Fault test functionality with DUT as Server" + + def pics_TC_EEVSE_2_5(self): + """ This function returns a list of PICS for this test case that must be True for the test to be run""" + # In this case - there is no feature flags needed to run this test case + return None + + def steps_TC_EEVSE_2_5(self) -> list[TestStep]: + steps = [ + TestStep("1", "Commissioning, already done", is_commissioning=True), + TestStep("2", "TH reads TestEventTriggersEnabled attribute from General Diagnostics Cluster. Verify that TestEventTriggersEnabled attribute has a value of 1 (True)"), + TestStep("3", "TH sends TestEventTrigger command to General Diagnostics Cluster on Endpoint 0 with EnableKey field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER_KEY and EventTrigger field set to PIXIT.EEVSE.TEST_EVENT_TRIGGER for Basic Functionality Test Event"), + TestStep("3a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("3b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("3c", "TH reads from the DUT the FaultState attribute. Verify value is 0x00 (NoError)"), + TestStep("4", "TH sends command EnableCharging with ChargingEnabledUntil=Null, minimumChargeCurrent=6000, maximumChargeCurrent=60000"), + TestStep("4a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("4b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x01 (ChargingEnabled)"), + TestStep("5", "TH sends command StartDiagnostics. Verify that command is rejected with Failure"), + TestStep("6", "TH sends command Disable."), + TestStep("6a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("6b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("7", "TH sends command StartDiagnostics. Verify that command is accepted with Success"), + TestStep("7a", "TH reads from the DUT the SupplyState attribute. Verify value is 0x04 (DisabledDiagnostics)"), + TestStep("8", "A few seconds later 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 Diagnostics Complete Event"), + TestStep("8a", "TH reads from the DUT the State attribute. Verify value is 0x00 (NotPluggedIn)"), + TestStep("8b", "TH reads from the DUT the SupplyState attribute. Verify value is 0x00 (Disabled)"), + TestStep("9", "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."), + ] + + return steps + + @async_test_body + async def test_TC_EEVSE_2_5(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("3a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("3b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("3c") + await self.check_evse_attribute("FaultState", Clusters.EnergyEvse.Enums.FaultStateEnum.kNoError) + + self.step("4") + charge_until = NullValue + min_charge_current = 6000 + max_charge_current = 60000 + await self.send_enable_charge_command(charge_until=charge_until, min_charge=min_charge_current, max_charge=max_charge_current) + + self.step("4a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("4b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kChargingEnabled) + + self.step("5") + # Check we get a failure because the state needs to be Disabled to run a Diagnostic + await self.send_start_diagnostics_command(expected_status=Status.Failure) + + self.step("6") + await self.send_disable_command() + + self.step("6a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("6b") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("7") + await self.send_start_diagnostics_command() + + self.step("7a") + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabledDiagnostics) + + self.step("8") + await self.send_test_event_trigger_evse_diagnostics_complete() + + self.step("8a") + await self.check_evse_attribute("State", Clusters.EnergyEvse.Enums.StateEnum.kNotPluggedIn) + + self.step("8b") + # It should stay disabled after a diagnostics session + await self.check_evse_attribute("SupplyState", Clusters.EnergyEvse.Enums.SupplyStateEnum.kDisabled) + + self.step("9") + 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 new file mode 100644 index 00000000000000..fb6bfa1f714d13 --- /dev/null +++ b/src/python_testing/TC_EEVSE_Utils.py @@ -0,0 +1,209 @@ +# +# Copyright (c) 2023 Project CHIP Authors +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import logging +import queue + +import chip.clusters as Clusters +from chip.clusters import ClusterObjects as ClusterObjects +from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction +from chip.interaction_model import InteractionModelError, Status +from mobly import asserts + +logger = logging.getLogger(__name__) + + +class EventChangeCallback: + def __init__(self, expected_cluster: ClusterObjects): + self._q = queue.Queue() + self._expected_cluster = expected_cluster + + async def start(self, dev_ctrl, node_id: int, endpoint: int): + self._subscription = await dev_ctrl.ReadEvent(node_id, + events=[(endpoint, self._expected_cluster, True)], reportInterval=(1, 5), + fabricFiltered=False, keepSubscriptions=True, autoResubscribe=False) + self._subscription.SetEventUpdateCallback(self.__call__) + + def __call__(self, res: EventReadResult, transaction: SubscriptionTransaction): + if res.Status == Status.Success and res.Header.ClusterId == self._expected_cluster.id: + logging.info( + f'Got subscription report for event on cluster {self._expected_cluster}: {res.Data}') + self._q.put(res) + + def WaitForEventReport(self, expected_event: ClusterObjects.ClusterEvent): + try: + res = self._q.get(block=True, timeout=10) + except queue.Empty: + asserts.fail("Failed to receive a report for the event {}".format(expected_event)) + + asserts.assert_equal(res.Header.ClusterId, expected_event.cluster_id, "Expected cluster ID not found in event report") + asserts.assert_equal(res.Header.EventId, expected_event.event_id, "Expected event ID not found in event report") + return res.Data + + +class EEVSEBaseTestHelper: + + async def read_evse_attribute_expect_success(self, endpoint: int = None, attribute: str = ""): + full_attr = getattr(Clusters.EnergyEvse.Attributes, attribute) + cluster = Clusters.Objects.EnergyEvse + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=full_attr) + + async def check_evse_attribute(self, attribute, expected_value, endpoint: int = None): + value = await self.read_evse_attribute_expect_success(endpoint=endpoint, attribute=attribute) + asserts.assert_equal(value, expected_value, + f"Unexpected '{attribute}' value - expected {expected_value}, was {value}") + + async def get_supported_energy_evse_attributes(self, endpoint: int = None): + return await self.read_evse_attribute_expect_success(endpoint, "AttributeList") + + async def write_user_max_charge(self, endpoint: int = None, user_max_charge: int = 0): + if endpoint is None: + endpoint = self.matter_test_config.endpoint + 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") + + async def send_enable_charge_command(self, endpoint: int = None, charge_until: int = None, timedRequestTimeoutMs: int = 3000, + min_charge: int = None, max_charge: int = None, expected_status: Status = Status.Success): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.EnableCharging( + chargingEnabledUntil=charge_until, + minimumChargeCurrent=min_charge, + maximumChargeCurrent=max_charge), + endpoint=endpoint, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + 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: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.Disable(), + endpoint=endpoint, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + 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): + try: + await self.send_single_cmd(cmd=Clusters.EnergyEvse.Commands.StartDiagnostics(), + endpoint=endpoint, + timedRequestTimeoutMs=timedRequestTimeoutMs) + + except InteractionModelError as e: + asserts.assert_equal(e.status, expected_status, "Unexpected error returned") + + async def send_test_event_triggers(self, enableKey: bytes = None, eventTrigger=0x0099000000000000): + # get the test event enable key or assume the default + # This can be passed in on command line using + # --hex-arg enableKey:000102030405060708090a0b0c0d0e0f + if enableKey is None: + if 'enableKey' not in self.matter_test_config.global_test_params: + enableKey = bytes([b for b in range(16)]) + else: + enableKey = self.matter_test_config.global_test_params['enableKey'] + + try: + # GeneralDiagnosics cluster is meant to be on Endpoint 0 (Root) + await self.send_single_cmd(endpoint=0, + cmd=Clusters.GeneralDiagnostics.Commands.TestEventTrigger( + enableKey, + eventTrigger) + ) + + except InteractionModelError as e: + asserts.fail(f"Unexpected error returned - {e.status}") + + async def check_test_event_triggers_enabled(self): + full_attr = Clusters.GeneralDiagnostics.Attributes.TestEventTriggersEnabled + cluster = Clusters.Objects.GeneralDiagnostics + # GeneralDiagnosics cluster is meant to be on Endpoint 0 (Root) + test_event_enabled = await self.read_single_attribute_check_success(endpoint=0, cluster=cluster, attribute=full_attr) + asserts.assert_equal(test_event_enabled, True, "TestEventTriggersEnabled is False") + + async def send_test_event_trigger_basic(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000000) + + async def send_test_event_trigger_basic_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000001) + + async def send_test_event_trigger_pluggedin(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000002) + + async def send_test_event_trigger_pluggedin_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000003) + + async def send_test_event_trigger_charge_demand(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000004) + + async def send_test_event_trigger_charge_demand_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000005) + + async def send_test_event_trigger_evse_ground_fault(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000010) + + async def send_test_event_trigger_evse_over_temperature_fault(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000011) + + async def send_test_event_trigger_evse_fault_clear(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000012) + + async def send_test_event_trigger_evse_diagnostics_complete(self): + await self.send_test_event_triggers(eventTrigger=0x0099000000000020) + + def validate_energy_transfer_started_event(self, event_data, session_id, expected_state, expected_max_charge): + asserts.assert_equal(session_id, event_data.sessionID, + f"EnergyTransferStarted event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EnergyTransferStarted event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(expected_max_charge, event_data.maximumCurrent, + f"EnergyTransferStarted event maximumCurrent was {event_data.maximumCurrent}, expected {expected_max_charge}") + + def validate_energy_transfer_stopped_event(self, event_data, session_id, expected_state, expected_reason): + asserts.assert_equal(session_id, event_data.sessionID, + f"EnergyTransferStopped event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EnergyTransferStopped event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(expected_reason, event_data.reason, + f"EnergyTransferStopped event reason was {event_data.reason}, expected {expected_reason}") + + def validate_ev_connected_event(self, event_data, session_id): + asserts.assert_equal(session_id, event_data.sessionID, + f"EvConnected event session ID was {event_data.sessionID}, expected {session_id}") + + def validate_ev_not_detected_event(self, event_data, session_id, expected_state, expected_duration, expected_charged): + asserts.assert_equal(session_id, event_data.sessionID, + f"EvNotDetected event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"EvNotDetected event event State was {event_data.state} expected {expected_state}") + asserts.assert_greater_equal(event_data.sessionDuration, expected_duration, + f"EvNotDetected event sessionDuration was {event_data.sessionDuration}, expected >= {expected_duration}") + asserts.assert_greater_equal(event_data.sessionEnergyCharged, expected_charged, + f"EvNotDetected event sessionEnergyCharged was {event_data.sessionEnergyCharged}, expected >= {expected_charged}") + + def validate_evse_fault_event(self, event_data, session_id, expected_state, previous_fault, current_fault): + asserts.assert_equal(session_id, event_data.sessionID, + f"Fault event session ID was {event_data.sessionID}, expected {session_id}") + asserts.assert_equal(expected_state, event_data.state, + f"Fault event State was {event_data.state} expected {expected_state}") + asserts.assert_equal(event_data.faultStatePreviousState, previous_fault, + f"Fault event faultStatePreviousState was {event_data.faultStatePreviousState}, expected {previous_fault}") + asserts.assert_equal(event_data.faultStateCurrentState, current_fault, + f"Fault event faultStateCurrentState was {event_data.faultStateCurrentState}, expected {current_fault}") diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp index e38b7822557b91..5d06d5a29ffe3b 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp @@ -10431,192 +10431,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value) namespace DeviceEnergyManagement { namespace Attributes { -namespace ESAType { - -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::DeviceEnergyManagement::ESATypeEnum * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::DeviceEnergyManagement::ESATypeEnum value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE); -} - -} // namespace ESAType - -namespace ESACanGenerate { - -EmberAfStatus Get(chip::EndpointId endpoint, bool * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, bool value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, writable, ZCL_BOOLEAN_ATTRIBUTE_TYPE); -} - -} // namespace ESACanGenerate - -namespace ESAState { - -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::DeviceEnergyManagement::ESAStateEnum * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::DeviceEnergyManagement::ESAStateEnum value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE); -} - -} // namespace ESAState - -namespace AbsMinPower { - -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, writable, ZCL_POWER_MW_ATTRIBUTE_TYPE); -} - -} // namespace AbsMinPower - -namespace AbsMaxPower { - -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, writable, ZCL_POWER_MW_ATTRIBUTE_TYPE); -} - -} // namespace AbsMaxPower - -namespace FeatureMap { - -EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::DeviceEnergyManagement::Id, Id, writable, ZCL_BITMAP32_ATTRIBUTE_TYPE); -} - -} // namespace FeatureMap - namespace ClusterRevision { EmberAfStatus Get(chip::EndpointId endpoint, uint16_t * value) @@ -10654,65 +10468,11 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value) namespace EnergyEvse { namespace Attributes { -namespace State { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::StateEnum value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, - const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace State - -namespace SupplyState { +namespace NumberOfWeeklyTargets { -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::SupplyStateEnum * value) +EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value) { - using Traits = NumericAttributeTraits; + using Traits = NumericAttributeTraits; Traits::StorageType temp; uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); @@ -10724,9 +10484,9 @@ EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::Su *value = Traits::StorageToWorking(temp); return status; } -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::SupplyStateEnum value) +EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value) { - using Traits = NumericAttributeTraits; + using Traits = NumericAttributeTraits; if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) { return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; @@ -10734,16 +10494,16 @@ EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::Su Traits::StorageType storageValue; Traits::WorkingToStorage(value, storageValue); uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE); + return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); } -} // namespace SupplyState +} // namespace NumberOfWeeklyTargets -namespace FaultState { +namespace NumberOfDailyTargets { -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::FaultStateEnum * value) +EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value) { - using Traits = NumericAttributeTraits; + using Traits = NumericAttributeTraits; Traits::StorageType temp; uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); @@ -10755,9 +10515,9 @@ EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::Fa *value = Traits::StorageToWorking(temp); return status; } -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::FaultStateEnum value) +EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value) { - using Traits = NumericAttributeTraits; + using Traits = NumericAttributeTraits; if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) { return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; @@ -10765,1027 +10525,10 @@ EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::Fa Traits::StorageType storageValue; Traits::WorkingToStorage(value, storageValue); uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENUM8_ATTRIBUTE_TYPE); -} - -} // namespace FaultState - -namespace ChargingEnabledUntil { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_EPOCH_S_ATTRIBUTE_TYPE); + return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); } -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_EPOCH_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace ChargingEnabledUntil - -namespace DischargingEnabledUntil { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_EPOCH_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_EPOCH_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace DischargingEnabledUntil - -namespace CircuitCapacity { - -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE); -} - -} // namespace CircuitCapacity - -namespace MinimumChargeCurrent { - -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE); -} - -} // namespace MinimumChargeCurrent - -namespace MaximumChargeCurrent { - -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE); -} - -} // namespace MaximumChargeCurrent - -namespace MaximumDischargeCurrent { - -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE); -} - -} // namespace MaximumDischargeCurrent - -namespace UserMaximumChargeCurrent { - -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_AMPERAGE_MA_ATTRIBUTE_TYPE); -} - -} // namespace UserMaximumChargeCurrent - -namespace RandomizationDelayWindow { - -EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ELAPSED_S_ATTRIBUTE_TYPE); -} - -} // namespace RandomizationDelayWindow - -namespace NumberOfWeeklyTargets { - -EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -} // namespace NumberOfWeeklyTargets - -namespace NumberOfDailyTargets { - -EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE); -} - -} // namespace NumberOfDailyTargets - -namespace NextChargeStartTime { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_EPOCH_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_EPOCH_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace NextChargeStartTime - -namespace NextChargeTargetTime { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_EPOCH_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_EPOCH_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace NextChargeTargetTime - -namespace NextChargeRequiredEnergy { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENERGY_MWH_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENERGY_MWH_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace NextChargeRequiredEnergy - -namespace NextChargeTargetSoC { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, chip::Percent value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_PERCENT_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_PERCENT_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace NextChargeTargetSoC - -namespace ApproximateEVEfficiency { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_INT16U_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_INT16U_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace ApproximateEVEfficiency - -namespace StateOfCharge { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, chip::Percent value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_PERCENT_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_PERCENT_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace StateOfCharge - -namespace BatteryCapacity { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENERGY_MWH_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENERGY_MWH_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace BatteryCapacity - -namespace VehicleID { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - uint8_t zclString[32 + 1]; - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, zclString, sizeof(zclString)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - size_t length = emberAfStringLength(zclString); - if (length == NumericAttributeTraits::kNullValue) - { - value.SetNull(); - return EMBER_ZCL_STATUS_SUCCESS; - } - auto & span = value.SetNonNull(); - - VerifyOrReturnError(span.size() == 32, EMBER_ZCL_STATUS_INVALID_DATA_TYPE); - memcpy(span.data(), &zclString[1], 32); - span.reduce_size(length); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, chip::CharSpan value) -{ - static_assert(32 < NumericAttributeTraits::kNullValue, "value.size() might be too big"); - VerifyOrReturnError(value.size() <= 32, EMBER_ZCL_STATUS_CONSTRAINT_ERROR); - uint8_t zclString[32 + 1]; - auto length = static_cast(value.size()); - Encoding::Put8(zclString, length); - memcpy(&zclString[1], value.data(), value.size()); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, zclString, ZCL_CHAR_STRING_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - uint8_t zclString[1] = { 0xFF }; - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, zclString, ZCL_CHAR_STRING_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace VehicleID - -namespace SessionID { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_INT32U_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_INT32U_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace SessionID - -namespace SessionDuration { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ELAPSED_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ELAPSED_S_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace SessionDuration - -namespace SessionEnergyCharged { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENERGY_MWH_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENERGY_MWH_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace SessionEnergyCharged - -namespace SessionEnergyDischarged { - -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (Traits::IsNullValue(temp)) - { - value.SetNull(); - } - else - { - value.SetNonNull() = Traits::StorageToWorking(temp); - } - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ true, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENERGY_MWH_ATTRIBUTE_TYPE); -} - -EmberAfStatus SetNull(chip::EndpointId endpoint) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType value; - Traits::SetNull(value); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(value); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_ENERGY_MWH_ATTRIBUTE_TYPE); -} - -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value) -{ - if (value.IsNull()) - { - return SetNull(endpoint); - } - - return Set(endpoint, value.Value()); -} - -} // namespace SessionEnergyDischarged - -namespace FeatureMap { - -EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value) -{ - using Traits = NumericAttributeTraits; - Traits::StorageType temp; - uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp); - EmberAfStatus status = emberAfReadAttribute(endpoint, Clusters::EnergyEvse::Id, Id, readable, sizeof(temp)); - VerifyOrReturnError(EMBER_ZCL_STATUS_SUCCESS == status, status); - if (!Traits::CanRepresentValue(/* isNullable = */ false, temp)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - *value = Traits::StorageToWorking(temp); - return status; -} -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value) -{ - using Traits = NumericAttributeTraits; - if (!Traits::CanRepresentValue(/* isNullable = */ false, value)) - { - return EMBER_ZCL_STATUS_CONSTRAINT_ERROR; - } - Traits::StorageType storageValue; - Traits::WorkingToStorage(value, storageValue); - uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue); - return emberAfWriteAttribute(endpoint, Clusters::EnergyEvse::Id, Id, writable, ZCL_BITMAP32_ATTRIBUTE_TYPE); -} - -} // namespace FeatureMap +} // namespace NumberOfDailyTargets namespace ClusterRevision { diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h index 39bac607884451..f4bc00b17f2743 100644 --- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h +++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h @@ -2027,36 +2027,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value); namespace DeviceEnergyManagement { namespace Attributes { -namespace ESAType { -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::DeviceEnergyManagement::ESATypeEnum * value); // ESATypeEnum -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::DeviceEnergyManagement::ESATypeEnum value); -} // namespace ESAType - -namespace ESACanGenerate { -EmberAfStatus Get(chip::EndpointId endpoint, bool * value); // boolean -EmberAfStatus Set(chip::EndpointId endpoint, bool value); -} // namespace ESACanGenerate - -namespace ESAState { -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::DeviceEnergyManagement::ESAStateEnum * value); // ESAStateEnum -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::DeviceEnergyManagement::ESAStateEnum value); -} // namespace ESAState - -namespace AbsMinPower { -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value); // power_mw -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -} // namespace AbsMinPower - -namespace AbsMaxPower { -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value); // power_mw -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -} // namespace AbsMaxPower - -namespace FeatureMap { -EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value); // bitmap32 -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -} // namespace FeatureMap - namespace ClusterRevision { EmberAfStatus Get(chip::EndpointId endpoint, uint16_t * value); // int16u EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value); @@ -2068,68 +2038,6 @@ EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value); namespace EnergyEvse { namespace Attributes { -namespace State { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // StateEnum -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::StateEnum value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, - const chip::app::DataModel::Nullable & value); -} // namespace State - -namespace SupplyState { -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::SupplyStateEnum * value); // SupplyStateEnum -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::SupplyStateEnum value); -} // namespace SupplyState - -namespace FaultState { -EmberAfStatus Get(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::FaultStateEnum * value); // FaultStateEnum -EmberAfStatus Set(chip::EndpointId endpoint, chip::app::Clusters::EnergyEvse::FaultStateEnum value); -} // namespace FaultState - -namespace ChargingEnabledUntil { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // epoch_s -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace ChargingEnabledUntil - -namespace DischargingEnabledUntil { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // epoch_s -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace DischargingEnabledUntil - -namespace CircuitCapacity { -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value); // amperage_ma -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -} // namespace CircuitCapacity - -namespace MinimumChargeCurrent { -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value); // amperage_ma -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -} // namespace MinimumChargeCurrent - -namespace MaximumChargeCurrent { -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value); // amperage_ma -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -} // namespace MaximumChargeCurrent - -namespace MaximumDischargeCurrent { -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value); // amperage_ma -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -} // namespace MaximumDischargeCurrent - -namespace UserMaximumChargeCurrent { -EmberAfStatus Get(chip::EndpointId endpoint, int64_t * value); // amperage_ma -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -} // namespace UserMaximumChargeCurrent - -namespace RandomizationDelayWindow { -EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value); // elapsed_s -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -} // namespace RandomizationDelayWindow - namespace NumberOfWeeklyTargets { EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value); // int8u EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value); @@ -2140,95 +2048,6 @@ EmberAfStatus Get(chip::EndpointId endpoint, uint8_t * value); // int8u EmberAfStatus Set(chip::EndpointId endpoint, uint8_t value); } // namespace NumberOfDailyTargets -namespace NextChargeStartTime { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // epoch_s -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace NextChargeStartTime - -namespace NextChargeTargetTime { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // epoch_s -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace NextChargeTargetTime - -namespace NextChargeRequiredEnergy { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // energy_mwh -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace NextChargeRequiredEnergy - -namespace NextChargeTargetSoC { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // percent -EmberAfStatus Set(chip::EndpointId endpoint, chip::Percent value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace NextChargeTargetSoC - -namespace ApproximateEVEfficiency { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // int16u -EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace ApproximateEVEfficiency - -namespace StateOfCharge { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // percent -EmberAfStatus Set(chip::EndpointId endpoint, chip::Percent value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace StateOfCharge - -namespace BatteryCapacity { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // energy_mwh -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace BatteryCapacity - -namespace VehicleID { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // char_string -EmberAfStatus Set(chip::EndpointId endpoint, chip::CharSpan value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace VehicleID - -namespace SessionID { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // int32u -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace SessionID - -namespace SessionDuration { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // elapsed_s -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace SessionDuration - -namespace SessionEnergyCharged { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // energy_mwh -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace SessionEnergyCharged - -namespace SessionEnergyDischarged { -EmberAfStatus Get(chip::EndpointId endpoint, DataModel::Nullable & value); // energy_mwh -EmberAfStatus Set(chip::EndpointId endpoint, int64_t value); -EmberAfStatus SetNull(chip::EndpointId endpoint); -EmberAfStatus Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable & value); -} // namespace SessionEnergyDischarged - -namespace FeatureMap { -EmberAfStatus Get(chip::EndpointId endpoint, uint32_t * value); // bitmap32 -EmberAfStatus Set(chip::EndpointId endpoint, uint32_t value); -} // namespace FeatureMap - namespace ClusterRevision { EmberAfStatus Get(chip::EndpointId endpoint, uint16_t * value); // int16u EmberAfStatus Set(chip::EndpointId endpoint, uint16_t value);