- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
588 lines
21 KiB
Python
588 lines
21 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# Copyright Pionix GmbH and Contributors to EVerest
|
|
|
|
"""
|
|
Integration smoke tests for OCPP 2.1 R04 - Configure DER control settings at Charging Station.
|
|
|
|
Covers the routing/availability behaviour end-to-end over a real websocket connection:
|
|
* CS returns NotImplemented (CALLERROR) when DCDERCtrlr/ACDERCtrlr is not available
|
|
* CS returns Accepted when DCDERCtrlr.Available=true and controlType is in ModesSupported
|
|
* CS returns NotSupported when the controlType is not in ModesSupported
|
|
* GetDERControl with no stored controls returns NotFound
|
|
* ClearDERControl with no controls returns NotFound
|
|
|
|
These tests exercise the wiring done in libocpp: the DERControl functional block
|
|
is conditionally instantiated based on the DERCtrlr.Available variable, and
|
|
SetDERControl/GetDERControl/ClearDERControl messages are routed to it.
|
|
"""
|
|
|
|
import pytest
|
|
import logging
|
|
|
|
# fmt: off
|
|
from everest.testing.core_utils.controller.test_controller_interface import TestController
|
|
|
|
from ocpp.v21 import call as call21
|
|
from ocpp.v21 import call_result as call_result21
|
|
from ocpp.v21.enums import *
|
|
from ocpp.v21.datatypes import *
|
|
from ocpp.routing import on, create_route_map
|
|
|
|
from everest.testing.ocpp_utils.fixtures import *
|
|
from everest_test_utils import * # must come before v21 datatype re-imports
|
|
from ocpp.v21.enums import Action, DERControlEnumType, DERControlStatusEnumType, DERUnitEnumType
|
|
|
|
from everest.testing.core_utils._configuration.libocpp_configuration_helper import GenericOCPP2XConfigAdjustment
|
|
from everest.testing.ocpp_utils.charge_point_utils import TestUtility, OcppTestConfiguration, wait_for_and_validate
|
|
# fmt: on
|
|
|
|
log = logging.getLogger("derControlTest")
|
|
|
|
# The DER controller component configs were removed from the shipped library and
|
|
# e2e config sets pending a proper interface to provide them dynamically. Without
|
|
# DCDERCtrlr_1 present in the device model these end-to-end tests cannot configure
|
|
# DER, so skip the whole module for now. The DER behaviour stays covered by the
|
|
# libocpp unit tests (DERControlTest), which carry their own DCDERCtrlr_1 fixture.
|
|
pytestmark = pytest.mark.skip(
|
|
reason="DER controller config removed pending a dynamic DER interface; "
|
|
"covered by libocpp DERControlTest unit tests for now"
|
|
)
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# R04 - DER not available: SetDERControl is rejected as NotImplemented
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config(
|
|
get_everest_config_path_str("everest-config-ocpp201.yaml")
|
|
)
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
),
|
|
]
|
|
)
|
|
)
|
|
async def test_set_der_control_der_not_available_not_implemented(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""R04: Without DCDERCtrlr/ACDERCtrlr Available, SetDERControl returns CALLERROR NotImplemented.
|
|
|
|
The Python ocpp library suppresses CallErrors by default (returning None)
|
|
rather than raising, so we assert that the response is None. The underlying
|
|
CALLError is logged as a warning by the library.
|
|
"""
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint()
|
|
|
|
freq_droop = FreqDroopType(
|
|
priority=0,
|
|
over_freq=61.0,
|
|
under_freq=59.0,
|
|
over_droop=5.0,
|
|
under_droop=5.0,
|
|
response_time=3.0,
|
|
)
|
|
|
|
r = await charge_point_v21.set_der_control_req(
|
|
is_default=True,
|
|
control_id="ctrl-1",
|
|
control_type=DERControlEnumType.freq_droop,
|
|
freq_droop=freq_droop,
|
|
)
|
|
# CALLError NotImplemented is suppressed to None by the library
|
|
assert r is None
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# R04.FR.02 - DER available + supported controlType: Accepted
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config(
|
|
get_everest_config_path_str("everest-config-ocpp201.yaml")
|
|
)
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrAvailable", "Actual"
|
|
),
|
|
"true",
|
|
),
|
|
# ModesSupported is ReadOnly; test relies on the default list in
|
|
# DCDERCtrlr_1.json: FreqDroop,VoltWatt,LimitMaxDischarge,VoltVar,
|
|
# FixedVar,EnterService,Gradients
|
|
]
|
|
)
|
|
)
|
|
async def test_set_der_control_supported_type_accepted(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""R04.FR.02: With DCDERCtrlr Available and FreqDroop in ModesSupported, set default FreqDroop returns Accepted."""
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint()
|
|
|
|
freq_droop = FreqDroopType(
|
|
priority=0,
|
|
over_freq=61.0,
|
|
under_freq=59.0,
|
|
over_droop=5.0,
|
|
under_droop=5.0,
|
|
response_time=3.0,
|
|
)
|
|
|
|
r: call_result21.SetDERControl = await charge_point_v21.set_der_control_req(
|
|
is_default=True,
|
|
control_id="ctrl-default-fd",
|
|
control_type=DERControlEnumType.freq_droop,
|
|
freq_droop=freq_droop,
|
|
)
|
|
assert r.status == DERControlStatusEnumType.accepted
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# R04.FR.01 - DER available but controlType not in ModesSupported: NotSupported
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config(
|
|
get_everest_config_path_str("everest-config-ocpp201.yaml")
|
|
)
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrAvailable", "Actual"
|
|
),
|
|
"true",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrModesSupported", "Actual"
|
|
),
|
|
# HFMustTrip is intentionally NOT in this list
|
|
"FreqDroop,VoltWatt",
|
|
),
|
|
]
|
|
)
|
|
)
|
|
async def test_set_der_control_unsupported_type_not_supported(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""R04.FR.01: With DCDERCtrlr Available but controlType not in ModesSupported, returns NotSupported.
|
|
|
|
HFMustTrip is intentionally NOT in the default ModesSupported list
|
|
(DCDERCtrlr_1.json), so this should return NotSupported.
|
|
"""
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint()
|
|
|
|
# HFMustTrip curve with yUnit=Not_Applicable (valid shape, unsupported type).
|
|
# NOTE: StrEnum auto-converts "Not_Applicable" -> Python attr `not__applicable` (double underscore).
|
|
curve = DERCurveType(
|
|
curve_data=[DERCurvePointsType(x=62.0, y=1.0)],
|
|
priority=0,
|
|
y_unit=DERUnitEnumType.not__applicable,
|
|
)
|
|
|
|
r: call_result21.SetDERControl = await charge_point_v21.set_der_control_req(
|
|
is_default=True,
|
|
control_id="ctrl-hf-trip",
|
|
control_type=DERControlEnumType.hf_must_trip,
|
|
curve=curve,
|
|
)
|
|
assert r.status == DERControlStatusEnumType.not_supported
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# R04.FR.30 - GetDERControl with no stored controls: NotFound
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config(
|
|
get_everest_config_path_str("everest-config-ocpp201.yaml")
|
|
)
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrAvailable", "Actual"
|
|
),
|
|
"true",
|
|
),
|
|
# ModesSupported is ReadOnly; test relies on the default list in
|
|
# DCDERCtrlr_1.json: FreqDroop,VoltWatt,LimitMaxDischarge,VoltVar,
|
|
# FixedVar,EnterService,Gradients
|
|
]
|
|
)
|
|
)
|
|
async def test_get_der_control_no_controls_not_found(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""R04.FR.30: GetDERControl when no matching controls exist returns NotFound."""
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint()
|
|
|
|
r: call_result21.GetDERControl = await charge_point_v21.get_der_control_req(
|
|
request_id=42,
|
|
control_type=DERControlEnumType.freq_droop,
|
|
)
|
|
assert r.status == DERControlStatusEnumType.not_found
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# R04.FR.41 - ClearDERControl with no matching controls: NotFound
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config(
|
|
get_everest_config_path_str("everest-config-ocpp201.yaml")
|
|
)
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrAvailable", "Actual"
|
|
),
|
|
"true",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrModesSupported", "Actual"
|
|
),
|
|
"FreqDroop,VoltWatt",
|
|
),
|
|
]
|
|
)
|
|
)
|
|
async def test_clear_der_control_no_match_not_found(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""R04.FR.41: ClearDERControl when no matching controls exist returns NotFound."""
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint()
|
|
|
|
r: call_result21.ClearDERControl = await charge_point_v21.clear_der_control_req(
|
|
is_default=True,
|
|
control_type=DERControlEnumType.freq_droop,
|
|
)
|
|
assert r.status == DERControlStatusEnumType.not_found
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# TC_R_102 end-to-end - Clearing controlTypes
|
|
# (R04.FR.02, FR.30, FR.33, FR.41, FR.44, FR.45)
|
|
#
|
|
# Exercises the cross-message state evolution that the unit tests can't:
|
|
# Set defaults of two distinct types, Clear-by-type one of them, verify the
|
|
# other survives, then Clear-all and verify the table is empty. Multi-row
|
|
# Report payload ordering itself is unit-tested in
|
|
# DERControlTest.GetDERControl_ReportOrdersMultiRowByIsSuperseded; this test
|
|
# locks in that the persisted state evolves correctly across messages.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config(
|
|
get_everest_config_path_str("everest-config-ocpp201.yaml")
|
|
)
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrAvailable", "Actual"
|
|
),
|
|
"true",
|
|
),
|
|
# ModesSupported is ReadOnly; relies on the default list in
|
|
# DCDERCtrlr_1.json which includes both FreqDroop and VoltWatt.
|
|
]
|
|
)
|
|
)
|
|
async def test_tc_r_102_clear_by_type_then_clear_all(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""TC_R_102: Set two defaults of distinct types, clear one by type, verify
|
|
the other survives, then clear all and verify the table is empty.
|
|
"""
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint()
|
|
|
|
# Step 1: SetDERControl default FreqDroop (R04.FR.02)
|
|
freq_droop = FreqDroopType(
|
|
priority=0,
|
|
over_freq=61.0,
|
|
under_freq=59.0,
|
|
over_droop=5.0,
|
|
under_droop=5.0,
|
|
response_time=3.0,
|
|
)
|
|
r: call_result21.SetDERControl = await charge_point_v21.set_der_control_req(
|
|
is_default=True,
|
|
control_id="ctrl-default-fd",
|
|
control_type=DERControlEnumType.freq_droop,
|
|
freq_droop=freq_droop,
|
|
)
|
|
assert r.status == DERControlStatusEnumType.accepted
|
|
|
|
# Step 2: SetDERControl default VoltWatt
|
|
volt_watt_curve = DERCurveType(
|
|
curve_data=[DERCurvePointsType(x=240.0, y=100.0)],
|
|
priority=1,
|
|
y_unit=DERUnitEnumType.pct_maxw,
|
|
)
|
|
r = await charge_point_v21.set_der_control_req(
|
|
is_default=True,
|
|
control_id="ctrl-default-vw",
|
|
control_type=DERControlEnumType.volt_watt,
|
|
curve=volt_watt_curve,
|
|
)
|
|
assert r.status == DERControlStatusEnumType.accepted
|
|
|
|
# Step 3: GetDERControl FreqDroop -> Accepted (the row exists)
|
|
r_get: call_result21.GetDERControl = await charge_point_v21.get_der_control_req(
|
|
request_id=1,
|
|
is_default=True,
|
|
control_type=DERControlEnumType.freq_droop,
|
|
)
|
|
assert r_get.status == DERControlStatusEnumType.accepted
|
|
|
|
# Step 4: GetDERControl VoltWatt -> Accepted (the row exists)
|
|
r_get = await charge_point_v21.get_der_control_req(
|
|
request_id=2,
|
|
is_default=True,
|
|
control_type=DERControlEnumType.volt_watt,
|
|
)
|
|
assert r_get.status == DERControlStatusEnumType.accepted
|
|
|
|
# Step 5: ClearDERControl by type=FreqDroop (R04.FR.45)
|
|
r_clear: call_result21.ClearDERControl = (
|
|
await charge_point_v21.clear_der_control_req(
|
|
is_default=True,
|
|
control_type=DERControlEnumType.freq_droop,
|
|
)
|
|
)
|
|
assert r_clear.status == DERControlStatusEnumType.accepted
|
|
|
|
# Step 6: GetDERControl FreqDroop -> NotFound (the row was cleared, R04.FR.30)
|
|
r_get = await charge_point_v21.get_der_control_req(
|
|
request_id=3,
|
|
is_default=True,
|
|
control_type=DERControlEnumType.freq_droop,
|
|
)
|
|
assert r_get.status == DERControlStatusEnumType.not_found
|
|
|
|
# Step 7: GetDERControl VoltWatt -> Accepted (untouched by the targeted clear)
|
|
r_get = await charge_point_v21.get_der_control_req(
|
|
request_id=4,
|
|
is_default=True,
|
|
control_type=DERControlEnumType.volt_watt,
|
|
)
|
|
assert r_get.status == DERControlStatusEnumType.accepted
|
|
|
|
# Step 8: ClearDERControl with no controlType / controlId,
|
|
# isDefault=true -> clear ALL matching defaults (R04.FR.44)
|
|
r_clear = await charge_point_v21.clear_der_control_req(is_default=True)
|
|
assert r_clear.status == DERControlStatusEnumType.accepted
|
|
|
|
# Step 9: GetDERControl any default -> NotFound (table is empty)
|
|
r_get = await charge_point_v21.get_der_control_req(
|
|
request_id=5,
|
|
is_default=True,
|
|
control_type=DERControlEnumType.volt_watt,
|
|
)
|
|
assert r_get.status == DERControlStatusEnumType.not_found
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# TC_R_103 - SetDERControl with unsupported controlType returns NotSupported.
|
|
# (R04.FR.01) Locks in the spec contract that prior log analysis showed the
|
|
# certification tool can violate by picking a supported type for this step.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config(
|
|
get_everest_config_path_str("everest-config-ocpp201.yaml")
|
|
)
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrAvailable", "Actual"
|
|
),
|
|
"true",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrModesSupported", "Actual"
|
|
),
|
|
# WattVar deliberately omitted.
|
|
"FreqDroop,VoltWatt,VoltVar,FixedVar,LimitMaxDischarge,EnterService,Gradients",
|
|
),
|
|
]
|
|
)
|
|
)
|
|
async def test_tc_r_103_unsupported_control_type_returns_not_supported(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""TC_R_103 step 2: SetDERControl with controlType not in ModesSupported -> NotSupported."""
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint()
|
|
|
|
watt_var_curve = DERCurveType(
|
|
curve_data=[DERCurvePointsType(x=100.0, y=50.0)],
|
|
priority=0,
|
|
y_unit=DERUnitEnumType.pct_max_var,
|
|
)
|
|
|
|
r: call_result21.SetDERControl = await charge_point_v21.set_der_control_req(
|
|
is_default=True,
|
|
control_id="ctrl-wv",
|
|
control_type=DERControlEnumType.watt_var,
|
|
curve=watt_var_curve,
|
|
)
|
|
assert r.status == DERControlStatusEnumType.not_supported
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# TC_R_104 - GetDERControl Accepted MUST be followed by an outbound
|
|
# ReportDERControl message. (R04.FR.32-33). Regression guard for a libocpp bug
|
|
# where MessageType::ReportDERControl was missing from the enum, causing the
|
|
# outbound Call to throw and emit a FormationViolation CallError instead.
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config(
|
|
get_everest_config_path_str("everest-config-ocpp201.yaml")
|
|
)
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
),
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"DCDERCtrlr_1", "DCDERCtrlrAvailable", "Actual"
|
|
),
|
|
"true",
|
|
),
|
|
]
|
|
)
|
|
)
|
|
async def test_tc_r_104_get_emits_report_der_control(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""TC_R_104: SetDERControl(default FreqDroop), then GetDERControl. CS must
|
|
respond with GetDERControlResponse(status=Accepted) AND then emit a separate
|
|
ReportDERControl message carrying the stored control.
|
|
"""
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint()
|
|
|
|
freq_droop = FreqDroopType(
|
|
priority=0,
|
|
over_freq=61.0,
|
|
under_freq=59.0,
|
|
over_droop=5.0,
|
|
under_droop=5.0,
|
|
response_time=3.0,
|
|
)
|
|
r_set: call_result21.SetDERControl = await charge_point_v21.set_der_control_req(
|
|
is_default=True,
|
|
control_id="ctrl-r104",
|
|
control_type=DERControlEnumType.freq_droop,
|
|
freq_droop=freq_droop,
|
|
)
|
|
assert r_set.status == DERControlStatusEnumType.accepted
|
|
|
|
r_get: call_result21.GetDERControl = await charge_point_v21.get_der_control_req(
|
|
request_id=104,
|
|
is_default=True,
|
|
control_type=DERControlEnumType.freq_droop,
|
|
)
|
|
assert r_get.status == DERControlStatusEnumType.accepted
|
|
|
|
# The wire-level message that the libocpp bug used to suppress.
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"ReportDERControl",
|
|
{"requestId": 104},
|
|
)
|