Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- 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
This commit is contained in:
File diff suppressed because it is too large
Load Diff
597
tools/EVerest-main/tests/ocpp_tests/test_sets/ocpp16/booting.py
Normal file
597
tools/EVerest-main/tests/ocpp_tests/test_sets/ocpp16/booting.py
Normal file
@@ -0,0 +1,597 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
from everest.testing.core_utils.controller.test_controller_interface import (
|
||||
TestController,
|
||||
)
|
||||
# fmt: off
|
||||
from ocpp.routing import create_route_map, on
|
||||
from ocpp.v16.enums import *
|
||||
from ocpp.v16 import call
|
||||
from datetime import datetime, timezone
|
||||
import asyncio
|
||||
import logging
|
||||
import pytest
|
||||
from validations import (validate_standard_start_transaction,
|
||||
validate_standard_stop_transaction,
|
||||
validate_boot_notification
|
||||
)
|
||||
from everest.testing.ocpp_utils.charge_point_utils import wait_for_and_validate, TestUtility, OcppTestConfiguration
|
||||
from everest.testing.ocpp_utils.fixtures import charge_point_v16
|
||||
from everest.testing.ocpp_utils.charge_point_v16 import ChargePoint16
|
||||
from everest.testing.ocpp_utils.central_system import CentralSystem
|
||||
from everest.testing.core_utils._configuration.libocpp_configuration_helper import GenericOCPP16ConfigAdjustment
|
||||
from everest_test_utils import *
|
||||
# fmt: on
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stop_pending_transactions(
|
||||
test_config: OcppTestConfiguration,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_utility: TestUtility,
|
||||
test_controller: TestController,
|
||||
central_system_v16: CentralSystem,
|
||||
):
|
||||
logging.info("######### test_stop_pending_transactions #########")
|
||||
|
||||
# start charging session
|
||||
test_controller.plug_in()
|
||||
|
||||
# send RemoteStartTransaction.req
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
# expect StatusNotification with status charging
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
call.StatusNotification(
|
||||
1, ChargePointErrorCode.no_error, ChargePointStatus.charging
|
||||
),
|
||||
)
|
||||
|
||||
# charge for some time...
|
||||
logging.debug("Charging for a while...")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
test_controller.stop()
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
test_controller.start()
|
||||
|
||||
charge_point_v16 = await central_system_v16.wait_for_chargepoint(
|
||||
wait_for_bootnotification=False
|
||||
)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# expect StopTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StopTransaction",
|
||||
call.StopTransaction(0, "", 1, Reason.power_loss),
|
||||
validate_standard_stop_transaction,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-security-profile-1.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_change_authorization_key_in_pending(
|
||||
test_config: OcppTestConfiguration,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info(
|
||||
"######### test_change_authorization_key_in_pending #########")
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_pending(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=10,
|
||||
status=RegistrationStatus.pending,
|
||||
)
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_accepted(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=5,
|
||||
status=RegistrationStatus.accepted,
|
||||
)
|
||||
|
||||
central_system_v16.function_overrides.append(
|
||||
("on_boot_notification", on_boot_notification_pending)
|
||||
)
|
||||
|
||||
test_controller.start()
|
||||
charge_point_v16 = await central_system_v16.wait_for_chargepoint()
|
||||
charge_point_v16.pipe = True
|
||||
|
||||
response = await charge_point_v16.get_configuration_req()
|
||||
assert len(response.configuration_key) > 20
|
||||
|
||||
await charge_point_v16.change_configuration_req(
|
||||
key="MeterValueSampleInterval", value="10"
|
||||
)
|
||||
await charge_point_v16.change_configuration_req(
|
||||
key="AuthorizationKey", value="DEADBEEFDEADBEEF"
|
||||
)
|
||||
|
||||
# wait for reconnect
|
||||
await central_system_v16.wait_for_chargepoint(wait_for_bootnotification=False)
|
||||
charge_point_v16 = central_system_v16.chargepoint
|
||||
|
||||
setattr(charge_point_v16, "on_boot_notification",
|
||||
on_boot_notification_accepted)
|
||||
central_system_v16.chargepoint.route_map = create_route_map(
|
||||
central_system_v16.chargepoint
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"BootNotification",
|
||||
call.BootNotification(
|
||||
test_config.charge_point_info.charge_point_model,
|
||||
charge_box_serial_number=test_config.charge_point_info.charge_point_id,
|
||||
charge_point_vendor=test_config.charge_point_info.charge_point_vendor,
|
||||
firmware_version=test_config.charge_point_info.firmware_version,
|
||||
),
|
||||
validate_boot_notification,
|
||||
)
|
||||
|
||||
# expect StatusNotification.req with status available
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
call.StatusNotification(
|
||||
1, ChargePointErrorCode.no_error, ChargePointStatus.available
|
||||
),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "Heartbeat", call.Heartbeat()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-security-profile-1.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_start_stop_in_pending(
|
||||
test_config: OcppTestConfiguration,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info(
|
||||
"######### test_change_authorization_key_in_pending #########")
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_pending(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=10,
|
||||
status=RegistrationStatus.pending,
|
||||
)
|
||||
|
||||
central_system_v16.function_overrides.append(
|
||||
("on_boot_notification", on_boot_notification_pending)
|
||||
)
|
||||
|
||||
test_controller.start()
|
||||
charge_point_v16 = await central_system_v16.wait_for_chargepoint()
|
||||
charge_point_v16.pipe = True
|
||||
|
||||
await charge_point_v16.remote_start_transaction_req(id_tag="DEADBEEF")
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "RemoteStartTransaction", {
|
||||
"status": "Rejected"}
|
||||
)
|
||||
|
||||
await charge_point_v16.remote_stop_transaction_req(transaction_id=20)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "RemoteStopTransaction", {
|
||||
"status": "Rejected"}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_boot_notification_rejected(
|
||||
test_config: OcppTestConfiguration,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_boot_notification_rejected #########")
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_rejected(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=10,
|
||||
status=RegistrationStatus.rejected,
|
||||
)
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_accepted(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=5,
|
||||
status=RegistrationStatus.accepted,
|
||||
)
|
||||
|
||||
central_system_v16.function_overrides.append(
|
||||
("on_boot_notification", on_boot_notification_rejected)
|
||||
)
|
||||
|
||||
test_controller.start()
|
||||
charge_point_v16: ChargePoint16 = await central_system_v16.wait_for_chargepoint()
|
||||
charge_point_v16.pipe = True
|
||||
|
||||
setattr(charge_point_v16, "on_boot_notification",
|
||||
on_boot_notification_accepted)
|
||||
central_system_v16.chargepoint.route_map = create_route_map(
|
||||
central_system_v16.chargepoint
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"BootNotification",
|
||||
call.BootNotification(
|
||||
test_config.charge_point_info.charge_point_model,
|
||||
charge_box_serial_number=test_config.charge_point_info.charge_point_id,
|
||||
charge_point_vendor=test_config.charge_point_info.charge_point_vendor,
|
||||
firmware_version=test_config.charge_point_info.firmware_version,
|
||||
),
|
||||
validate_boot_notification,
|
||||
)
|
||||
|
||||
# expect StatusNotification.req with status available
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
call.StatusNotification(
|
||||
1, ChargePointErrorCode.no_error, ChargePointStatus.available
|
||||
),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "Heartbeat", call.Heartbeat()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_boot_notification_callerror(
|
||||
test_config: OcppTestConfiguration,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_boot_notification_callerror #########")
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_accepted(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=5,
|
||||
status=RegistrationStatus.accepted,
|
||||
)
|
||||
|
||||
# Provoke a CALLERROR as a response to a BootNotification.req
|
||||
central_system_v16.function_overrides.append(
|
||||
("on_boot_notification", None))
|
||||
|
||||
test_controller.start()
|
||||
charge_point_v16: ChargePoint16 = await central_system_v16.wait_for_chargepoint()
|
||||
charge_point_v16.pipe = True
|
||||
|
||||
setattr(charge_point_v16, "on_boot_notification",
|
||||
on_boot_notification_accepted)
|
||||
central_system_v16.chargepoint.route_map = create_route_map(
|
||||
central_system_v16.chargepoint
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"BootNotification",
|
||||
call.BootNotification(
|
||||
test_config.charge_point_info.charge_point_model,
|
||||
charge_box_serial_number=test_config.charge_point_info.charge_point_id,
|
||||
charge_point_vendor=test_config.charge_point_info.charge_point_vendor,
|
||||
firmware_version=test_config.charge_point_info.firmware_version,
|
||||
),
|
||||
validate_boot_notification,
|
||||
timeout=100,
|
||||
)
|
||||
|
||||
# expect StatusNotification.req with status available
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
call.StatusNotification(
|
||||
1, ChargePointErrorCode.no_error, ChargePointStatus.available
|
||||
),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "Heartbeat", call.Heartbeat()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_boot_notification_no_response(
|
||||
test_config: OcppTestConfiguration,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_boot_notification_no_response #########")
|
||||
|
||||
async def route_message(msg):
|
||||
return
|
||||
|
||||
# do not respond at all
|
||||
central_system_v16.function_overrides.append(
|
||||
("route_message", route_message))
|
||||
|
||||
test_controller.start()
|
||||
charge_point_v16: ChargePoint16 = await central_system_v16.wait_for_chargepoint()
|
||||
charge_point_v16.pipe = True
|
||||
|
||||
# this is the second BootNotification.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"BootNotification",
|
||||
call.BootNotification(
|
||||
test_config.charge_point_info.charge_point_model,
|
||||
charge_box_serial_number=test_config.charge_point_info.charge_point_id,
|
||||
charge_point_vendor=test_config.charge_point_info.charge_point_vendor,
|
||||
firmware_version=test_config.charge_point_info.firmware_version,
|
||||
),
|
||||
validate_boot_notification,
|
||||
timeout=100,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-security-profile-2.yaml")
|
||||
)
|
||||
@pytest.mark.source_certs_dir(Path(__file__).parent / "../everest-aux/certs")
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.csms_tls
|
||||
@pytest.mark.ocpp_config_adaptions(
|
||||
GenericOCPP16ConfigAdjustment(
|
||||
[("Internal", "VerifyCsmsCommonName", False)])
|
||||
)
|
||||
async def test_initiate_message_in_pending(
|
||||
test_config: OcppTestConfiguration,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_initiate_message_in_pending #########")
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_pending(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=10,
|
||||
status=RegistrationStatus.pending,
|
||||
)
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_accepted(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=5,
|
||||
status=RegistrationStatus.accepted,
|
||||
)
|
||||
|
||||
central_system_v16.function_overrides.append(
|
||||
("on_boot_notification", on_boot_notification_pending)
|
||||
)
|
||||
|
||||
test_utility.forbidden_actions.append("SecurityEventNotification")
|
||||
|
||||
test_controller.start()
|
||||
charge_point_v16: ChargePoint16 = await central_system_v16.wait_for_chargepoint()
|
||||
charge_point_v16.pipe = True
|
||||
|
||||
await charge_point_v16.change_configuration_req(key="CpoName", value="VENID")
|
||||
|
||||
await charge_point_v16.extended_trigger_message_req(
|
||||
requested_message=MessageTrigger.status_notification
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
call.StatusNotification(
|
||||
1, ChargePointErrorCode.no_error, ChargePointStatus.available
|
||||
),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
await charge_point_v16.extended_trigger_message_req(
|
||||
requested_message=MessageTrigger.boot_notification
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"BootNotification",
|
||||
call.BootNotification(
|
||||
test_config.charge_point_info.charge_point_model,
|
||||
charge_box_serial_number=test_config.charge_point_info.charge_point_id,
|
||||
charge_point_vendor=test_config.charge_point_info.charge_point_vendor,
|
||||
firmware_version=test_config.charge_point_info.firmware_version,
|
||||
),
|
||||
validate_boot_notification,
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
await charge_point_v16.extended_trigger_message_req(
|
||||
requested_message=MessageTrigger.heartbeat
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "Heartbeat", call.Heartbeat()
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
await charge_point_v16.trigger_message_req(
|
||||
requested_message=MessageTrigger.diagnostics_status_notification
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"DiagnosticsStatusNotification",
|
||||
call.DiagnosticsStatusNotification(DiagnosticsStatus.idle),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
await charge_point_v16.trigger_message_req(
|
||||
requested_message=MessageTrigger.firmware_status_notification
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"FirmwareStatusNotification",
|
||||
call.FirmwareStatusNotification(FirmwareStatus.idle),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
await charge_point_v16.trigger_message_req(
|
||||
requested_message=MessageTrigger.status_notification
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
call.StatusNotification(
|
||||
1, ChargePointErrorCode.no_error, ChargePointStatus.available
|
||||
),
|
||||
)
|
||||
|
||||
await charge_point_v16.extended_trigger_message_req(
|
||||
requested_message=MessageTrigger.sign_charge_point_certificate
|
||||
)
|
||||
# expect ExtendedTriggerMessage.conf with status Accepted
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"ExtendedTriggerMessage",
|
||||
call_result.ExtendedTriggerMessage(TriggerMessageStatus.accepted),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "SignCertificate", {}
|
||||
)
|
||||
|
||||
setattr(charge_point_v16, "on_boot_notification",
|
||||
on_boot_notification_accepted)
|
||||
central_system_v16.chargepoint.route_map = create_route_map(
|
||||
central_system_v16.chargepoint
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"BootNotification",
|
||||
call.BootNotification(
|
||||
test_config.charge_point_info.charge_point_model,
|
||||
charge_box_serial_number=test_config.charge_point_info.charge_point_id,
|
||||
charge_point_vendor=test_config.charge_point_info.charge_point_vendor,
|
||||
firmware_version=test_config.charge_point_info.firmware_version,
|
||||
),
|
||||
validate_boot_notification,
|
||||
)
|
||||
|
||||
test_utility.forbidden_actions.clear()
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"SecurityEventNotification",
|
||||
{"type": "StartupOfTheDevice"},
|
||||
)
|
||||
|
||||
# expect StatusNotification.req with status available
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
call.StatusNotification(
|
||||
1, ChargePointErrorCode.no_error, ChargePointStatus.available
|
||||
),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "Heartbeat", call.Heartbeat()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_boot_notification_rejected_and_call_by_csms(
|
||||
test_config: OcppTestConfiguration,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_rejected(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=10,
|
||||
status=RegistrationStatus.rejected,
|
||||
)
|
||||
|
||||
central_system_v16.function_overrides.append(
|
||||
("on_boot_notification", on_boot_notification_rejected)
|
||||
)
|
||||
|
||||
test_controller.start()
|
||||
charge_point_v16: ChargePoint16 = await central_system_v16.wait_for_chargepoint()
|
||||
charge_point_v16.pipe = True
|
||||
|
||||
# Response to this message is not allowed
|
||||
test_utility.forbidden_actions.append("RemoteStartTransaction")
|
||||
|
||||
t = threading.Thread(
|
||||
target=asyncio.run,
|
||||
args=(
|
||||
charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
),
|
||||
),
|
||||
)
|
||||
t.start()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "BootNotification", {}
|
||||
)
|
||||
428
tools/EVerest-main/tests/ocpp_tests/test_sets/ocpp16/broken.py
Normal file
428
tools/EVerest-main/tests/ocpp_tests/test_sets/ocpp16/broken.py
Normal file
@@ -0,0 +1,428 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
import pytest
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from everest.testing.core_utils.controller.test_controller_interface import (
|
||||
TestController,
|
||||
)
|
||||
from everest.testing.core_utils.everest_core import EverestCore, Requirement
|
||||
from everest.testing.core_utils.probe_module import ProbeModule
|
||||
from ocpp.v16 import call, call_result
|
||||
from ocpp.v16.enums import *
|
||||
from ocpp.v16.datatypes import IdTagInfo
|
||||
from ocpp.messages import Call, _DecimalEncoder
|
||||
from ocpp.charge_point import snake_to_camel_case
|
||||
from ocpp.routing import on, create_route_map
|
||||
|
||||
# fmt: off
|
||||
from validations import wait_for_callerror_and_validate, validate_boot_notification
|
||||
from everest.testing.ocpp_utils.fixtures import charge_point_v16, test_utility
|
||||
from everest.testing.ocpp_utils.central_system import CentralSystem
|
||||
from everest.testing.ocpp_utils.charge_point_v16 import ChargePoint16
|
||||
from everest.testing.ocpp_utils.charge_point_utils import wait_for_and_validate, TestUtility
|
||||
from everest_test_utils import *
|
||||
# fmt: on
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_payload_field(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_missing_payload_field #########")
|
||||
|
||||
payload = call.ChangeConfiguration(key="WebSocketPingInterval", value="0")
|
||||
camel_case_payload = snake_to_camel_case(asdict(payload))
|
||||
|
||||
call_msg = Call(
|
||||
unique_id=str(charge_point_v16._unique_id_generator()),
|
||||
action=payload.__class__.__name__,
|
||||
payload=remove_nones(camel_case_payload),
|
||||
)
|
||||
|
||||
# remove a required payload field
|
||||
del call_msg.payload["value"]
|
||||
|
||||
await send_message_without_validation(charge_point_v16, call_msg)
|
||||
|
||||
assert await wait_for_callerror_and_validate(
|
||||
test_utility, charge_point_v16, "FormationViolation"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.skip(reason="libocpp currently does not support this")
|
||||
@pytest.mark.asyncio
|
||||
async def test_additional_payload_field(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_additional_payload_field #########")
|
||||
|
||||
payload = call.ChangeConfiguration(key="WebSocketPingInterval", value="0")
|
||||
camel_case_payload = snake_to_camel_case(asdict(payload))
|
||||
|
||||
call_msg = Call(
|
||||
unique_id=str(charge_point_v16._unique_id_generator()),
|
||||
action=payload.__class__.__name__,
|
||||
payload=remove_nones(camel_case_payload),
|
||||
)
|
||||
|
||||
# add a payload field
|
||||
call_msg.payload["additional"] = "123"
|
||||
|
||||
await send_message_without_validation(charge_point_v16, call_msg)
|
||||
|
||||
# FIXME: this message seems to be accepted, should be rejected according to spec...
|
||||
assert await wait_for_callerror_and_validate(
|
||||
test_utility, charge_point_v16, "FormationViolation"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrong_payload_type(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_wrong_payload_type #########")
|
||||
|
||||
# key should just be string, but here we set it to array of string
|
||||
payload = call.ChangeConfiguration(
|
||||
key=["WebSocketPingInterval"], value="0")
|
||||
camel_case_payload = snake_to_camel_case(asdict(payload))
|
||||
|
||||
call_msg = Call(
|
||||
unique_id=str(charge_point_v16._unique_id_generator()),
|
||||
action=payload.__class__.__name__,
|
||||
payload=remove_nones(camel_case_payload),
|
||||
)
|
||||
|
||||
await send_message_without_validation(charge_point_v16, call_msg)
|
||||
|
||||
assert await wait_for_callerror_and_validate(
|
||||
test_utility, charge_point_v16, "FormationViolation"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrong_auth_payload(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_wrong_auth_payload #########")
|
||||
|
||||
@on(Action.authorize)
|
||||
def on_authorize(**kwargs):
|
||||
# send an empty id_tag_info, this should not crash EVerest
|
||||
id_tag_info = {}
|
||||
res = call_result.Authorize(id_tag_info=id_tag_info)
|
||||
return res
|
||||
|
||||
setattr(charge_point_v16, "on_authorize", on_authorize)
|
||||
charge_point_v16.route_map = create_route_map(charge_point_v16)
|
||||
charge_point_v16.route_map[Action.authorize]["_skip_schema_validation"] = True
|
||||
|
||||
await charge_point_v16.change_configuration_req(
|
||||
key="AuthorizeRemoteTxRequests", value="true"
|
||||
)
|
||||
|
||||
test_controller.plug_in()
|
||||
|
||||
test_controller.swipe(test_config.authorization_info.valid_id_tag_1)
|
||||
# expect authorize.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"Authorize",
|
||||
call.Authorize(test_config.authorization_info.valid_id_tag_1),
|
||||
)
|
||||
|
||||
# this only works if we don't crash from the broken response
|
||||
test_controller.swipe(test_config.authorization_info.valid_id_tag_2)
|
||||
# expect authorize.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"Authorize",
|
||||
call.Authorize(test_config.authorization_info.valid_id_tag_2),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.probe_module(
|
||||
connections={"ocpp_data_transfer": [Requirement("ocpp", "data_transfer")]}
|
||||
)
|
||||
@pytest.mark.inject_csms_mock
|
||||
@pytest.mark.asyncio
|
||||
async def test_data_transfer_with_probe_module(
|
||||
central_system_v16_standalone: CentralSystem, everest_core: EverestCore
|
||||
):
|
||||
logging.info("######### test_data_transfer_with_probe_module #########")
|
||||
|
||||
@on(Action.data_transfer)
|
||||
def on_data_transfer(**kwargs):
|
||||
logging.info(f"Received a data transfer message {datetime.now()}")
|
||||
req = call.DataTransfer(**kwargs)
|
||||
if req.vendor_id == "PIONIX" and req.message_id == "test_message":
|
||||
return call_result.DataTransfer(
|
||||
status=DataTransferStatus.accepted, data="Hello there"
|
||||
)
|
||||
elif req.vendor_id == "PIONIX" and req.message_id == "test_message_broken":
|
||||
# purposefully return a wrong payload
|
||||
return call_result.Authorize(id_tag_info={})
|
||||
return call_result.DataTransfer(
|
||||
status=DataTransferStatus.unknown_message_id, data="Please implement me"
|
||||
)
|
||||
|
||||
cs = central_system_v16_standalone.mock
|
||||
cs.on_data_transfer.side_effect = on_data_transfer
|
||||
|
||||
probe_module = ProbeModule(everest_core.get_runtime_session())
|
||||
probe_module.start()
|
||||
|
||||
await probe_module.wait_to_be_ready()
|
||||
|
||||
charge_point_v16 = await central_system_v16_standalone.wait_for_chargepoint()
|
||||
charge_point_v16.route_map[Action.data_transfer]["_skip_schema_validation"] = True
|
||||
|
||||
result = await probe_module.call_command(
|
||||
"ocpp_data_transfer",
|
||||
"data_transfer",
|
||||
{
|
||||
"request": {
|
||||
"vendor_id": "PIONIX",
|
||||
"message_id": "test_message",
|
||||
"data": "test",
|
||||
}
|
||||
},
|
||||
)
|
||||
assert "data" in result and "status" in result and result["status"] == "Accepted"
|
||||
|
||||
result = await probe_module.call_command(
|
||||
"ocpp_data_transfer",
|
||||
"data_transfer",
|
||||
{
|
||||
"request": {
|
||||
"vendor_id": "PIONIX",
|
||||
"message_id": "test_message_unknown",
|
||||
"data": "test",
|
||||
}
|
||||
},
|
||||
)
|
||||
assert "status" in result and result["status"] == "UnknownMessageId"
|
||||
|
||||
result = await probe_module.call_command(
|
||||
"ocpp_data_transfer",
|
||||
"data_transfer",
|
||||
{
|
||||
"request": {
|
||||
"vendor_id": "PIONIX",
|
||||
"message_id": "test_message_broken",
|
||||
"data": "test",
|
||||
}
|
||||
},
|
||||
)
|
||||
assert "status" in result and result["status"] == "Rejected"
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_boot_notification_call_error(
|
||||
test_config,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_boot_notification_call_error #########")
|
||||
|
||||
test_controller.start()
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_error(**kwargs):
|
||||
raise InternalError()
|
||||
|
||||
@on(Action.boot_notification)
|
||||
def on_boot_notification_accepted(**kwargs):
|
||||
return call_result.BootNotification(
|
||||
current_time=datetime.now(timezone.utc).isoformat(),
|
||||
interval=5,
|
||||
status=RegistrationStatus.accepted,
|
||||
)
|
||||
|
||||
central_system_v16.function_overrides.append(
|
||||
("on_boot_notification", on_boot_notification_error)
|
||||
)
|
||||
charge_point_v16 = await central_system_v16.wait_for_chargepoint(
|
||||
wait_for_bootnotification=False
|
||||
)
|
||||
# charge_point_v16.route_map[Action.authorize]['_skip_schema_validation'] = True
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"BootNotification",
|
||||
call.BootNotification(
|
||||
charge_box_serial_number="cp001",
|
||||
charge_point_model="Yeti",
|
||||
charge_point_vendor="Pionix",
|
||||
firmware_version="0.1",
|
||||
),
|
||||
validate_boot_notification,
|
||||
)
|
||||
|
||||
central_system_v16.function_overrides.append(
|
||||
("on_boot_notification", on_boot_notification_accepted)
|
||||
)
|
||||
|
||||
logging.info("disconnect the ws connection...")
|
||||
test_controller.disconnect_websocket()
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
logging.info("connecting the ws connection")
|
||||
test_controller.connect_websocket()
|
||||
|
||||
# wait for reconnect
|
||||
charge_point_v16 = await central_system_v16.wait_for_chargepoint(
|
||||
wait_for_bootnotification=False
|
||||
)
|
||||
# charge_point_v16.route_map[Action.authorize]['_skip_schema_validation'] = True
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"BootNotification",
|
||||
call.BootNotification(
|
||||
charge_box_serial_number="cp001",
|
||||
charge_point_model="Yeti",
|
||||
charge_point_vendor="Pionix",
|
||||
firmware_version="0.1",
|
||||
),
|
||||
validate_boot_notification,
|
||||
timeout=70,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.inject_csms_mock
|
||||
async def test_start_transaction_call_error_or_timeout(
|
||||
test_config,
|
||||
central_system_v16: CentralSystem,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info(
|
||||
"######### test_start_transaction_call_error_or_timeout #########")
|
||||
|
||||
test_controller.start()
|
||||
|
||||
central_system_v16.mock.on_start_transaction.side_effect = [
|
||||
NotImplementedError(),
|
||||
NotImplementedError(),
|
||||
NotImplementedError(),
|
||||
NotImplementedError(),
|
||||
call_result.StartTransaction(
|
||||
transaction_id=1, id_tag_info=IdTagInfo(status=AuthorizationStatus.accepted)
|
||||
),
|
||||
]
|
||||
|
||||
charge_point_v16 = await central_system_v16.wait_for_chargepoint(
|
||||
wait_for_bootnotification=False
|
||||
)
|
||||
|
||||
test_controller.swipe("DEADBEEF")
|
||||
test_controller.plug_in()
|
||||
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "StartTransaction", {}
|
||||
)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
test_controller.plug_out()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "StopTransaction", {"transactionId": 1}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_too_long_payload_field(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_too_long_payload_field #########")
|
||||
|
||||
payload = call.ChangeConfiguration(
|
||||
key="ThisIsMuchLongerThan50charactersThisIsMuchLongerThan50charactersThisIsMuchLongerThan50charactersThisIsMuchLongerThan50charactersThisIsMuchLongerThan50characters", value="0")
|
||||
camel_case_payload = snake_to_camel_case(asdict(payload))
|
||||
|
||||
call_msg = Call(
|
||||
unique_id=str(charge_point_v16._unique_id_generator()),
|
||||
action=payload.__class__.__name__,
|
||||
payload=remove_nones(camel_case_payload),
|
||||
)
|
||||
|
||||
await send_message_without_validation(charge_point_v16, call_msg)
|
||||
|
||||
assert await wait_for_callerror_and_validate(
|
||||
test_utility, charge_point_v16, "FormationViolation"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-sil-ocpp.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_encoding_in_payload(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_invalid_encoding_in_payload #########")
|
||||
|
||||
# a malformed CALL should trigger a RpcFrameworkError CALLERROR
|
||||
call_msg = b"\xd8\x00\x00\x00"
|
||||
|
||||
async with charge_point_v16._call_lock:
|
||||
await charge_point_v16._send(call_msg)
|
||||
|
||||
assert await wait_for_callerror_and_validate(
|
||||
test_utility, charge_point_v16, "GenericError"
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,224 @@
|
||||
import pytest
|
||||
|
||||
from ocpp.v16.enums import AvailabilityType, AvailabilityStatus, ReservationStatus
|
||||
from ocpp.v16.call_result import ChangeAvailability
|
||||
|
||||
from everest.testing.ocpp_utils.charge_point_utils import (
|
||||
wait_for_and_validate,
|
||||
TestUtility,
|
||||
)
|
||||
from everest.testing.ocpp_utils.fixtures import *
|
||||
from everest_test_utils import get_everest_config_path_str
|
||||
from everest.testing.ocpp_utils.charge_point_v16 import ChargePoint16
|
||||
from everest.testing.core_utils.controller.test_controller_interface import (
|
||||
TestController,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_change_availability(
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_utility: TestUtility
|
||||
):
|
||||
|
||||
r: ChangeAvailability = await charge_point_v16.change_availability_req(
|
||||
type=AvailabilityType.inoperative, connector_id=1
|
||||
)
|
||||
|
||||
assert r.status == AvailabilityStatus.accepted
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 1, "status": "Unavailable", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
# verify the same request is accepted and no further StatusNotification.req is sent
|
||||
r: ChangeAvailability = await charge_point_v16.change_availability_req(
|
||||
type=AvailabilityType.inoperative, connector_id=1
|
||||
)
|
||||
|
||||
assert r.status == AvailabilityStatus.accepted
|
||||
|
||||
test_utility.messages.clear()
|
||||
test_utility.forbidden_actions.append("StatusNotification")
|
||||
|
||||
# verify connector can be set back to operational
|
||||
r: ChangeAvailability = await charge_point_v16.change_availability_req(
|
||||
type=AvailabilityType.operative, connector_id=1
|
||||
)
|
||||
|
||||
assert r.status == AvailabilityStatus.accepted
|
||||
|
||||
test_utility.forbidden_actions.clear()
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 1, "status": "Available", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-two-connectors.yaml")
|
||||
)
|
||||
async def test_change_availability_connector_zero(
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_utility: TestUtility
|
||||
):
|
||||
|
||||
r: ChangeAvailability = await charge_point_v16.change_availability_req(
|
||||
type=AvailabilityType.inoperative, connector_id=0
|
||||
)
|
||||
|
||||
assert r.status == AvailabilityStatus.accepted
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 0, "status": "Unavailable", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 1, "status": "Unavailable", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 2, "status": "Unavailable", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
# verify connector can be set back to operational
|
||||
r: ChangeAvailability = await charge_point_v16.change_availability_req(
|
||||
type=AvailabilityType.operative, connector_id=0
|
||||
)
|
||||
|
||||
assert r.status == AvailabilityStatus.accepted
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 0, "status": "Available", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 1, "status": "Available", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 2, "status": "Available", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-two-connectors.yaml")
|
||||
)
|
||||
async def test_change_availability_scheduled_in_preparing(
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_utility: TestUtility,
|
||||
test_controller: TestController,
|
||||
):
|
||||
|
||||
test_controller.plug_in()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 1, "status": "Preparing", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
r: ChangeAvailability = await charge_point_v16.change_availability_req(
|
||||
type=AvailabilityType.inoperative, connector_id=0
|
||||
)
|
||||
|
||||
assert r.status == AvailabilityStatus.scheduled
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 0, "status": "Unavailable", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 2, "status": "Unavailable", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
test_controller.plug_out()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 1, "status": "Unavailable", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_change_availability_scheduled_in_transaction(
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_utility: TestUtility,
|
||||
test_controller: TestController,
|
||||
):
|
||||
|
||||
test_controller.plug_in()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 1, "status": "Preparing", "errorCode": "NoError"},
|
||||
)
|
||||
|
||||
test_controller.swipe("DEADBEEF")
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "StartTransaction", {"connectorId": 1}
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
r: ChangeAvailability = await charge_point_v16.change_availability_req(
|
||||
type=AvailabilityType.inoperative, connector_id=1
|
||||
)
|
||||
|
||||
assert r.status == AvailabilityStatus.scheduled
|
||||
|
||||
test_controller.plug_out()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "StopTransaction", {
|
||||
"reason": "EVDisconnected"}
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StatusNotification",
|
||||
{"connectorId": 1, "status": "Unavailable", "errorCode": "NoError"},
|
||||
)
|
||||
@@ -0,0 +1,127 @@
|
||||
import pytest
|
||||
|
||||
from everest.testing.core_utils.controller.test_controller_interface import (
|
||||
TestController,
|
||||
)
|
||||
from everest.testing.ocpp_utils.charge_point_utils import (
|
||||
wait_for_and_validate,
|
||||
TestUtility,
|
||||
)
|
||||
from everest.testing.ocpp_utils.central_system import ChargePoint16
|
||||
from everest.testing.ocpp_utils.fixtures import test_utility, charge_point_v16
|
||||
from everest_test_utils import get_everest_config_path_str
|
||||
|
||||
from ocpp.v16 import call_result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-two-connectors.yaml")
|
||||
)
|
||||
async def test_meter_public_key(
|
||||
charge_point_v16: ChargePoint16, test_utility: TestUtility
|
||||
):
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKey1"])
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
call_result.GetConfiguration(
|
||||
[{"key": "MeterPublicKey1", "readonly": True, "value": "TESTPUBLICKEY1"}]
|
||||
),
|
||||
)
|
||||
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKey2"])
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
call_result.GetConfiguration(
|
||||
[{"key": "MeterPublicKey2", "readonly": True, "value": "TESTPUBLICKEY2"}]
|
||||
),
|
||||
)
|
||||
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKey3"])
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
{"unknownKey": ["MeterPublicKey3"]}
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
response : call_result.GetConfiguration = await charge_point_v16.get_configuration_req()
|
||||
|
||||
assert any(
|
||||
entry['key'] == "MeterPublicKey1" and entry['value'] == "TESTPUBLICKEY1"
|
||||
for entry in response.configuration_key)
|
||||
|
||||
assert any(
|
||||
entry['key'] == "MeterPublicKey2" and entry['value'] == "TESTPUBLICKEY2"
|
||||
for entry in response.configuration_key)
|
||||
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKey"])
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
{"unknownKey": ["MeterPublicKey"]}
|
||||
)
|
||||
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKeyMeterPublicKey1"])
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
{"unknownKey": ["MeterPublicKeyMeterPublicKey1"]}
|
||||
)
|
||||
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKey1X"])
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
{"unknownKey": ["MeterPublicKey1X"]}
|
||||
)
|
||||
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKey1X"])
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
{"unknownKey": ["MeterPublicKey1X"]}
|
||||
)
|
||||
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKeybanana"])
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
{"unknownKey": ["MeterPublicKeybanana"]}
|
||||
)
|
||||
|
||||
await charge_point_v16.get_configuration_req(key=["MeterPublicKey0"])
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetConfiguration",
|
||||
{"unknownKey": ["MeterPublicKey0"]}
|
||||
)
|
||||
|
||||
await charge_point_v16.change_configuration_req(
|
||||
key="MeterPublicKey1", value="TEST"
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"ChangeConfiguration",
|
||||
{"status": "Rejected"}
|
||||
)
|
||||
@@ -0,0 +1,347 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import logging
|
||||
import asyncio
|
||||
import getpass
|
||||
|
||||
from ocpp.v16 import call, call_result
|
||||
from ocpp.v16.enums import *
|
||||
|
||||
# fmt: off
|
||||
from validations import (validate_get_log)
|
||||
from everest.testing.ocpp_utils.charge_point_utils import wait_for_and_validate, TestUtility
|
||||
from everest.testing.ocpp_utils.fixtures import charge_point_v16
|
||||
from everest.testing.ocpp_utils.charge_point_v16 import ChargePoint16
|
||||
from everest_test_utils import *
|
||||
# fmt: on
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_diagnostics_retries(
|
||||
charge_point_v16: ChargePoint16, test_utility: TestUtility
|
||||
):
|
||||
logging.info("######### test_get_diagnostics_retries #########")
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# FIXME: make sure this port does not exist? or username and password are wrong?
|
||||
location = f"ftp://{getpass.getuser()}:12345@localhost:2121"
|
||||
start_time = datetime.now(timezone.utc)
|
||||
stop_time = start_time + timedelta(days=3)
|
||||
retries = 2
|
||||
retry_interval = 2
|
||||
|
||||
test_utility.messages.clear()
|
||||
await charge_point_v16.get_diagnostics_req(
|
||||
location=location,
|
||||
start_time=start_time.isoformat(),
|
||||
stop_time=stop_time.isoformat(),
|
||||
retries=retries,
|
||||
retry_interval=retry_interval,
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"DiagnosticsStatusNotification",
|
||||
call.DiagnosticsStatusNotification(DiagnosticsStatus.uploading),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"DiagnosticsStatusNotification",
|
||||
call.DiagnosticsStatusNotification(DiagnosticsStatus.upload_failed),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"DiagnosticsStatusNotification",
|
||||
call.DiagnosticsStatusNotification(DiagnosticsStatus.uploading),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"DiagnosticsStatusNotification",
|
||||
call.DiagnosticsStatusNotification(DiagnosticsStatus.upload_failed),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"DiagnosticsStatusNotification",
|
||||
call.DiagnosticsStatusNotification(DiagnosticsStatus.uploading),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"DiagnosticsStatusNotification",
|
||||
call.DiagnosticsStatusNotification(DiagnosticsStatus.upload_failed),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_upload_security_log_retries(
|
||||
charge_point_v16: ChargePoint16, test_utility: TestUtility
|
||||
):
|
||||
logging.info("######### test_upload_security_log_retries #########")
|
||||
|
||||
oldest_timestamp = datetime.now(timezone.utc)
|
||||
latest_timestamp = oldest_timestamp + timedelta(days=3)
|
||||
retries = 2
|
||||
retry_interval = 2
|
||||
|
||||
log = {
|
||||
"remoteLocation": f"ftp://{getpass.getuser()}:12345@localhost:2121",
|
||||
"oldestTimestamp": oldest_timestamp.isoformat(),
|
||||
"latestTimestamp": latest_timestamp.isoformat(),
|
||||
}
|
||||
|
||||
test_utility.messages.clear()
|
||||
await charge_point_v16.get_log_req(
|
||||
log=log,
|
||||
log_type=Log.security_log,
|
||||
retries=retries,
|
||||
retry_interval=retry_interval,
|
||||
request_id=1,
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetLog",
|
||||
call_result.GetLog(LogStatus.accepted),
|
||||
validate_get_log,
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"LogStatusNotification",
|
||||
call.LogStatusNotification(UploadLogStatus.uploading, 1),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"LogStatusNotification",
|
||||
call.LogStatusNotification(UploadLogStatus.upload_failure, 1),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"LogStatusNotification",
|
||||
call.LogStatusNotification(UploadLogStatus.uploading, 1),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"LogStatusNotification",
|
||||
call.LogStatusNotification(UploadLogStatus.upload_failure, 1),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"LogStatusNotification",
|
||||
call.LogStatusNotification(UploadLogStatus.uploading, 1),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"LogStatusNotification",
|
||||
call.LogStatusNotification(UploadLogStatus.upload_failure, 1),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_firwmare_update_retries(
|
||||
charge_point_v16: ChargePoint16, test_utility: TestUtility
|
||||
):
|
||||
# not supported when implemented security extensions
|
||||
logging.info("######### test_firwmare_update_retries #########")
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
retrieve_date = datetime.now(timezone.utc)
|
||||
location = f"ftp://{getpass.getuser()}:12345@localhost:2121/firmware_update.pnx"
|
||||
retries = 2
|
||||
retry_interval = 2
|
||||
|
||||
await charge_point_v16.update_firmware_req(
|
||||
location=location,
|
||||
retrieve_date=retrieve_date.isoformat(),
|
||||
retries=retries,
|
||||
retry_interval=retry_interval,
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"FirmwareStatusNotification",
|
||||
call.DiagnosticsStatusNotification(FirmwareStatus.downloading),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"FirmwareStatusNotification",
|
||||
call.DiagnosticsStatusNotification(FirmwareStatus.download_failed),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"FirmwareStatusNotification",
|
||||
call.DiagnosticsStatusNotification(FirmwareStatus.downloading),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"FirmwareStatusNotification",
|
||||
call.DiagnosticsStatusNotification(FirmwareStatus.download_failed),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"FirmwareStatusNotification",
|
||||
call.DiagnosticsStatusNotification(FirmwareStatus.downloading),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"FirmwareStatusNotification",
|
||||
call.DiagnosticsStatusNotification(FirmwareStatus.download_failed),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_signed_update_firmware_retries(
|
||||
test_config: OcppTestConfiguration,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
logging.info("######### test_signed_update_firmware_retries #########")
|
||||
|
||||
await asyncio.sleep(1)
|
||||
|
||||
await charge_point_v16.change_configuration_req(key="HeartbeatInterval", value="20")
|
||||
|
||||
certificate = open(test_config.certificate_info.mf_root_ca).read()
|
||||
|
||||
await charge_point_v16.install_certificate_req(
|
||||
certificate_type=CertificateUse.manufacturer_root_certificate,
|
||||
certificate=certificate,
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"InstallCertificate",
|
||||
call_result.InstallCertificate(CertificateStatus.accepted),
|
||||
)
|
||||
|
||||
location = f"ftp://{getpass.getuser()}:12345@localhost:2121/firmware_update.pnx"
|
||||
retries = 2
|
||||
retry_interval = 2
|
||||
retrieve_date_time = datetime.now(timezone.utc)
|
||||
mf_root_ca = open(test_config.certificate_info.mf_root_ca).read()
|
||||
fw_signature = open(test_config.firmware_info.update_file_signature).read()
|
||||
|
||||
firmware = {
|
||||
"location": location,
|
||||
"retrieveDateTime": retrieve_date_time.isoformat(),
|
||||
"signingCertificate": mf_root_ca,
|
||||
"signature": fw_signature,
|
||||
}
|
||||
|
||||
await charge_point_v16.signed_update_firmware_req(
|
||||
request_id=1, retries=retries, retry_interval=retry_interval, firmware=firmware
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"SignedUpdateFirmware",
|
||||
call_result.SignedUpdateFirmware(UpdateFirmwareStatus.accepted),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"SignedFirmwareStatusNotification",
|
||||
call.SignedFirmwareStatusNotification(FirmwareStatus.downloading, 1),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"SignedFirmwareStatusNotification",
|
||||
call.SignedFirmwareStatusNotification(
|
||||
FirmwareStatus.download_failed, 1),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"SignedFirmwareStatusNotification",
|
||||
call.SignedFirmwareStatusNotification(FirmwareStatus.downloading, 1),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"SignedFirmwareStatusNotification",
|
||||
call.SignedFirmwareStatusNotification(
|
||||
FirmwareStatus.download_failed, 1),
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"SignedFirmwareStatusNotification",
|
||||
call.SignedFirmwareStatusNotification(FirmwareStatus.downloading, 1),
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"SignedFirmwareStatusNotification",
|
||||
call.SignedFirmwareStatusNotification(
|
||||
FirmwareStatus.download_failed, 1),
|
||||
)
|
||||
|
||||
# no SignedFirmwareStatusNotification.req should be sent anymore
|
||||
test_utility.forbidden_actions.append("SignedFirmwareStatusNotification")
|
||||
test_utility.messages.clear()
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "Heartbeat", call.Heartbeat()
|
||||
)
|
||||
@@ -0,0 +1,147 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
from unittest.mock import call as mock_call, ANY
|
||||
import pytest
|
||||
from everest.testing.core_utils.common import Requirement
|
||||
from everest.testing.core_utils.controller.test_controller_interface import (
|
||||
TestController,
|
||||
)
|
||||
from everest.testing.core_utils.probe_module import ProbeModule
|
||||
|
||||
from ocpp.routing import create_route_map
|
||||
|
||||
from ocpp.v16 import call
|
||||
from ocpp.v16.enums import *
|
||||
|
||||
# fmt: off
|
||||
from validations import (validate_standard_start_transaction)
|
||||
from everest.testing.ocpp_utils.charge_point_utils import wait_for_and_validate, TestUtility, OcppTestConfiguration
|
||||
from everest.testing.ocpp_utils.fixtures import charge_point_v16
|
||||
from everest.testing.ocpp_utils.central_system import CentralSystem
|
||||
from everest.testing.ocpp_utils.charge_point_v16 import ChargePoint16
|
||||
from everest_test_utils import *
|
||||
# fmt: on
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_error_to_transaction_message(
|
||||
test_config: OcppTestConfiguration,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_utility: TestUtility,
|
||||
test_controller: TestController,
|
||||
):
|
||||
|
||||
setattr(charge_point_v16, "on_start_transaction", None)
|
||||
charge_point_v16.route_map = create_route_map(charge_point_v16)
|
||||
|
||||
await charge_point_v16.change_configuration_req(
|
||||
key="TransactionMessageAttempts", value="3"
|
||||
)
|
||||
|
||||
test_controller.plug_in()
|
||||
|
||||
test_controller.swipe(test_config.authorization_info.valid_id_tag_1)
|
||||
# expect authorize.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"Authorize",
|
||||
call.Authorize(test_config.authorization_info.valid_id_tag_1),
|
||||
)
|
||||
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
test_utility.forbidden_actions.append("StartTransaction")
|
||||
|
||||
test_controller.plug_out()
|
||||
|
||||
# expect StopTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility, charge_point_v16, "StopTransaction", {
|
||||
"reason": "EVDisconnected"}
|
||||
)
|
||||
|
||||
|
||||
async def wait_for_mock_called(mock, call=None, timeout=2):
|
||||
async def _await_called():
|
||||
while not mock.call_count or (call and call not in mock.mock_calls):
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
await asyncio.wait_for(_await_called(), timeout=timeout)
|
||||
|
||||
|
||||
@pytest.mark.ocpp_version("ocpp1.6")
|
||||
@pytest.mark.everest_core_config("everest-config-sil-ocpp.yaml")
|
||||
@pytest.mark.inject_csms_mock
|
||||
@pytest.mark.probe_module(connections={"ocpp": [Requirement("ocpp", "main")]})
|
||||
@pytest.mark.asyncio
|
||||
async def test_security_event_delivery_after_reconnect(
|
||||
everest_core, test_controller, central_system: CentralSystem
|
||||
):
|
||||
"""Tests A04.FR.02 of OCPP 1.6 Security White Paper"""
|
||||
|
||||
# Setup: Init Probe module, start EVerest and CSMS
|
||||
test_controller.start()
|
||||
csms_mock = central_system.mock
|
||||
|
||||
probe_module = ProbeModule(everest_core.get_runtime_session())
|
||||
|
||||
probe_module.start()
|
||||
await probe_module.wait_to_be_ready()
|
||||
await central_system.wait_for_chargepoint()
|
||||
|
||||
# Act: disconnect, send security event
|
||||
test_controller.disconnect_websocket()
|
||||
|
||||
csms_mock.on_security_event_notification.reset_mock()
|
||||
# Since on boot we expect a count of security events
|
||||
await probe_module.call_command(
|
||||
"ocpp", "security_event", {
|
||||
"type": "SecurityLogWasCleared", "info": "test_info"}
|
||||
)
|
||||
|
||||
# Verify: CSMS has not received any event (since offline), reconnect and verify event is received
|
||||
await asyncio.sleep(1)
|
||||
csms_mock.on_security_event_notification.assert_not_called()
|
||||
|
||||
test_controller.connect_websocket()
|
||||
|
||||
await wait_for_mock_called(
|
||||
csms_mock.on_security_event_notification,
|
||||
mock_call(tech_info="test_info", timestamp=ANY,
|
||||
type="SecurityLogWasCleared"),
|
||||
10,
|
||||
)
|
||||
6939
tools/EVerest-main/tests/ocpp_tests/test_sets/ocpp16/ocpp_compliance_tests.py
Executable file
6939
tools/EVerest-main/tests/ocpp_tests/test_sets/ocpp16/ocpp_compliance_tests.py
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,788 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from unittest.mock import Mock, call as mock_call, ANY
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from everest.testing.core_utils.common import Requirement
|
||||
from everest.testing.core_utils.everest_core import EverestCore
|
||||
from everest.testing.core_utils.probe_module import ProbeModule
|
||||
from everest.testing.ocpp_utils.central_system import CentralSystem
|
||||
from everest.testing.ocpp_utils.charge_point_v16 import ChargePoint16
|
||||
from ocpp.v16.call import SetChargingProfile
|
||||
|
||||
from everest.testing.core_utils._configuration.libocpp_configuration_helper import (
|
||||
GenericOCPP16ConfigAdjustment,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _OCPP16GenericInterfaceIntegrationEnvironment:
|
||||
csms_mock: Mock
|
||||
central_system: CentralSystem
|
||||
everest_core: EverestCore
|
||||
probe_module: ProbeModule
|
||||
probe_module_command_mocks: dict[str, dict[str, Mock]]
|
||||
charge_point: ChargePoint16
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def _env(
|
||||
everest_core,
|
||||
test_controller,
|
||||
central_system: CentralSystem,
|
||||
skip_implementation,
|
||||
overwrite_implementation,
|
||||
):
|
||||
test_controller.start()
|
||||
csms_mock = central_system.mock
|
||||
|
||||
probe_module = ProbeModule(everest_core.get_runtime_session())
|
||||
probe_module_command_mocks = {}
|
||||
|
||||
def _add_pm_command_mock(implementation_id, command, value, skip_implementation):
|
||||
skip = False
|
||||
if skip_implementation:
|
||||
if implementation_id in skip_implementation:
|
||||
to_skip = skip_implementation[implementation_id]
|
||||
if command in to_skip:
|
||||
logging.info(f"Skipping implementation of {command}")
|
||||
skip = True
|
||||
if not skip:
|
||||
if overwrite_implementation:
|
||||
logging.info(f"OVERW: {overwrite_implementation}")
|
||||
if implementation_id in overwrite_implementation:
|
||||
to_overwrite = overwrite_implementation[implementation_id]
|
||||
if command in to_overwrite:
|
||||
logging.info(
|
||||
f"Overwriting implementation of {command}")
|
||||
value = to_overwrite[command]
|
||||
probe_module_command_mocks.setdefault(implementation_id, {})[
|
||||
command
|
||||
] = Mock()
|
||||
probe_module_command_mocks[implementation_id][command].return_value = value
|
||||
probe_module.implement_command(
|
||||
implementation_id=implementation_id,
|
||||
command_name=command,
|
||||
handler=probe_module_command_mocks[implementation_id][command],
|
||||
)
|
||||
|
||||
for idx, evse_manager in enumerate(["evse_manager", "evse_manager_b"]):
|
||||
_add_pm_command_mock(
|
||||
evse_manager,
|
||||
"get_evse",
|
||||
{"id": idx + 1, "connectors": [{"id": 1}]},
|
||||
skip_implementation,
|
||||
)
|
||||
_add_pm_command_mock(evse_manager, "enable_disable",
|
||||
True, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
evse_manager, "authorize_response", None, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
evse_manager, "withdraw_authorization", None, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(evse_manager, "reserve",
|
||||
False, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
evse_manager, "cancel_reservation", None, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(evse_manager, "pause_charging",
|
||||
True, skip_implementation)
|
||||
_add_pm_command_mock(evse_manager, "resume_charging",
|
||||
True, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
evse_manager, "stop_transaction", True, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(evse_manager, "force_unlock",
|
||||
True, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
evse_manager, "update_allowed_energy_transfer_modes", None, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
evse_manager, "external_ready_to_start_charging", True, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
evse_manager, "set_plug_and_charge_configuration", True, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
"security", "get_leaf_expiry_days_count", 42, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
"security",
|
||||
"get_v2g_ocsp_request_data",
|
||||
{"ocsp_request_data_list": []},
|
||||
skip_implementation,
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
"security",
|
||||
"get_mo_ocsp_request_data",
|
||||
{"ocsp_request_data_list": []},
|
||||
skip_implementation,
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
"security", "install_ca_certificate", "Accepted", skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
"security", "delete_certificate", "Accepted", skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
"security", "update_leaf_certificate", "Accepted", skip_implementation
|
||||
)
|
||||
_add_pm_command_mock("security", "verify_certificate",
|
||||
"Valid", skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
"security",
|
||||
"get_installed_certificates",
|
||||
{"status": "Accepted", "certificate_hash_data_chain": []},
|
||||
skip_implementation,
|
||||
)
|
||||
_add_pm_command_mock("security", "update_ocsp_cache",
|
||||
None, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
"security", "is_ca_certificate_installed", False, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
"security",
|
||||
"generate_certificate_signing_request",
|
||||
{"status": "Accepted"},
|
||||
skip_implementation,
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
"security",
|
||||
"get_leaf_certificate_info",
|
||||
{"status": "Accepted"},
|
||||
skip_implementation,
|
||||
)
|
||||
_add_pm_command_mock("security", "get_verify_file",
|
||||
"", skip_implementation)
|
||||
_add_pm_command_mock("security", "verify_file_signature",
|
||||
True, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
"security",
|
||||
"get_all_valid_certificates_info",
|
||||
{"status": "NotFound", "info": []},
|
||||
skip_implementation,
|
||||
)
|
||||
_add_pm_command_mock(
|
||||
"security",
|
||||
"get_verify_location",
|
||||
"",
|
||||
skip_implementation,
|
||||
)
|
||||
_add_pm_command_mock("auth", "set_connection_timeout",
|
||||
None, skip_implementation)
|
||||
_add_pm_command_mock("auth", "withdraw_authorization",
|
||||
"Accepted", skip_implementation)
|
||||
_add_pm_command_mock("auth", "set_master_pass_group_id",
|
||||
None, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
"reservation", "cancel_reservation", "Accepted", skip_implementation
|
||||
)
|
||||
_add_pm_command_mock("reservation", "reserve_now",
|
||||
False, skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
"reservation", "exists_reservation", False, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock("system", "get_boot_reason",
|
||||
"PowerUp", skip_implementation)
|
||||
_add_pm_command_mock("system", "update_firmware",
|
||||
"Accepted", skip_implementation)
|
||||
_add_pm_command_mock(
|
||||
"system", "allow_firmware_installation", None, skip_implementation
|
||||
)
|
||||
_add_pm_command_mock("system", "upload_logs",
|
||||
"Accepted", skip_implementation)
|
||||
_add_pm_command_mock("system", "is_reset_allowed",
|
||||
True, skip_implementation)
|
||||
_add_pm_command_mock("system", "reset", None, skip_implementation)
|
||||
_add_pm_command_mock("system", "set_system_time",
|
||||
True, skip_implementation)
|
||||
|
||||
probe_module.start()
|
||||
await probe_module.wait_to_be_ready()
|
||||
for evse_manager in ["evse_manager", "evse_manager_b"]:
|
||||
probe_module.publish_variable(evse_manager, "ready", True)
|
||||
|
||||
await central_system.wait_for_chargepoint()
|
||||
|
||||
yield _OCPP16GenericInterfaceIntegrationEnvironment(
|
||||
csms_mock,
|
||||
central_system,
|
||||
everest_core,
|
||||
probe_module,
|
||||
probe_module_command_mocks,
|
||||
central_system.chargepoint,
|
||||
)
|
||||
test_controller.stop()
|
||||
|
||||
|
||||
class CSMSConnectionUtils:
|
||||
def __init__(self, central_system: CentralSystem):
|
||||
self._central_system = central_system
|
||||
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
if not self._central_system.ws_server.websockets:
|
||||
return False
|
||||
assert len(self._central_system.ws_server.websockets) == 1
|
||||
connection = next(iter(self._central_system.ws_server.websockets))
|
||||
return connection.open
|
||||
|
||||
|
||||
async def wait_for_mock_called(mock, call=None, timeout=2):
|
||||
async def _await_called():
|
||||
while not mock.call_count or (call and call not in mock.mock_calls):
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
await asyncio.wait_for(_await_called(), timeout=timeout)
|
||||
|
||||
|
||||
@pytest.mark.ocpp_version("ocpp1.6")
|
||||
@pytest.mark.everest_core_config("everest-config-ocpp16-probe-module.yaml")
|
||||
@pytest.mark.inject_csms_mock
|
||||
@pytest.mark.probe_module(connections={"ocpp": [Requirement("ocpp", "ocpp_generic")]})
|
||||
@pytest.mark.asyncio
|
||||
class TestOCPP16GenericInterfaceIntegration:
|
||||
|
||||
async def test_command_stop(self, _env):
|
||||
csms_connection = CSMSConnectionUtils(_env.central_system)
|
||||
assert csms_connection.is_connected
|
||||
res = await _env.probe_module.call_command("ocpp", "stop", None)
|
||||
assert res is True
|
||||
await asyncio.sleep(5)
|
||||
assert not csms_connection.is_connected
|
||||
|
||||
async def test_command_restart(self, _env):
|
||||
csms_connection = CSMSConnectionUtils(_env.central_system)
|
||||
await _env.probe_module.call_command("ocpp", "stop", None)
|
||||
await asyncio.sleep(5)
|
||||
assert not csms_connection.is_connected
|
||||
res = await _env.probe_module.call_command("ocpp", "restart", None)
|
||||
await asyncio.sleep(5)
|
||||
assert res is True
|
||||
assert csms_connection.is_connected
|
||||
|
||||
async def test_command_restart_denied(self, _env):
|
||||
csms_connection = CSMSConnectionUtils(_env.central_system)
|
||||
res = await _env.probe_module.call_command("ocpp", "restart", None)
|
||||
assert res is False
|
||||
assert csms_connection.is_connected
|
||||
|
||||
async def test_command_security_event(self, _env):
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"security_event",
|
||||
{
|
||||
"event": {
|
||||
"type": "SecurityLogWasCleared",
|
||||
"info": "integration_test_security_info",
|
||||
"critical": True,
|
||||
"timestamp": "2024-01-01T12:00:00",
|
||||
}
|
||||
},
|
||||
)
|
||||
assert res is None
|
||||
await wait_for_mock_called(
|
||||
_env.csms_mock.on_security_event_notification,
|
||||
mock_call(
|
||||
tech_info="integration_test_security_info",
|
||||
timestamp=ANY,
|
||||
type="SecurityLogWasCleared",
|
||||
),
|
||||
)
|
||||
|
||||
string_too_long = "WAYTOOLONG"*255
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"security_event",
|
||||
{
|
||||
"event": {
|
||||
"type": string_too_long,
|
||||
"info": string_too_long,
|
||||
"critical": True,
|
||||
"timestamp": "2024-01-01T12:00:00",
|
||||
}
|
||||
},
|
||||
)
|
||||
await wait_for_mock_called(
|
||||
_env.csms_mock.on_security_event_notification,
|
||||
mock_call(
|
||||
# truncated to 255 characters
|
||||
tech_info=string_too_long[0:255],
|
||||
timestamp=ANY,
|
||||
# truncated to 50 characters
|
||||
type=string_too_long[0:50],
|
||||
),
|
||||
)
|
||||
|
||||
assert (
|
||||
len(_env.csms_mock.on_security_event_notification.mock_calls) == 3
|
||||
) # we expect 3 because of the StartupOfTheDevice, SecurityLogWasCleared, StringTooLong
|
||||
|
||||
@pytest.mark.ocpp_config_adaptions(
|
||||
GenericOCPP16ConfigAdjustment(
|
||||
[("Custom", "ExampleConfigurationKey", "test_value")]
|
||||
)
|
||||
)
|
||||
async def test_command_get_variables(self, _env):
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"get_variables",
|
||||
{
|
||||
"requests": [
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": "IGNORED"},
|
||||
"variable": {"name": "ChargePointId"},
|
||||
}
|
||||
},
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "UNKNOWN"},
|
||||
},
|
||||
"attribute_type": "Target",
|
||||
},
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {
|
||||
"name": "ExampleConfigurationKey",
|
||||
"instance": "TO_BE_IGNORED",
|
||||
},
|
||||
},
|
||||
"attribute_type": "Target", # ignored
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
assert res == [
|
||||
{
|
||||
"attribute_type": "Actual",
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "ChargePointId"},
|
||||
},
|
||||
"status": "Accepted",
|
||||
"value": "cp001",
|
||||
},
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "UNKNOWN"},
|
||||
},
|
||||
"status": "UnknownVariable",
|
||||
},
|
||||
{
|
||||
"attribute_type": "Actual",
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "ExampleConfigurationKey"},
|
||||
},
|
||||
"status": "Accepted",
|
||||
"value": "test_value",
|
||||
},
|
||||
]
|
||||
|
||||
@pytest.mark.ocpp_config_adaptions(
|
||||
GenericOCPP16ConfigAdjustment(
|
||||
[("Custom", "ExampleConfigurationKey", "test_value")]
|
||||
)
|
||||
)
|
||||
async def test_command_set_variables(self, _env):
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"set_variables",
|
||||
{
|
||||
"requests": [
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": "IGNORED"},
|
||||
"variable": {"name": "RetryBackoffRandomRange"},
|
||||
},
|
||||
# not custom - will be Rejected
|
||||
"value": "99",
|
||||
},
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "UNKNOWN"},
|
||||
},
|
||||
# does not exist - will be UnknownVariable
|
||||
"attribute_type": "Target",
|
||||
"value": "test_value",
|
||||
},
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {
|
||||
"name": "ExampleConfigurationKey",
|
||||
"instance": "TO_BE_IGNORED",
|
||||
},
|
||||
},
|
||||
"attribute_type": "Target",
|
||||
"value": "unittest changed value",
|
||||
},
|
||||
],
|
||||
"source": "testcase",
|
||||
},
|
||||
)
|
||||
|
||||
assert res
|
||||
assert isinstance(res, list) and len(res) == 3
|
||||
assert res == [
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": "IGNORED"},
|
||||
"variable": {"name": "RetryBackoffRandomRange"},
|
||||
},
|
||||
"status": "Rejected",
|
||||
},
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "UNKNOWN"},
|
||||
},
|
||||
"status": "UnknownVariable",
|
||||
},
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {
|
||||
"instance": "TO_BE_IGNORED",
|
||||
"name": "ExampleConfigurationKey",
|
||||
},
|
||||
},
|
||||
"status": "Accepted",
|
||||
},
|
||||
]
|
||||
|
||||
# Verify value changed
|
||||
check = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"get_variables",
|
||||
{
|
||||
"requests": [
|
||||
{
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "ExampleConfigurationKey"},
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
assert check == [
|
||||
{
|
||||
"attribute_type": "Actual",
|
||||
"component_variable": {
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "ExampleConfigurationKey"},
|
||||
},
|
||||
"status": "Accepted",
|
||||
"value": "unittest changed value",
|
||||
}
|
||||
]
|
||||
|
||||
async def test_command_monitor_variables(self, _env):
|
||||
"""Test monitoring a configuraton variable as well as an event_data subscription."""
|
||||
|
||||
async def change_var(key: str, value: str):
|
||||
res = await _env.charge_point.change_configuration_req(key=key, value=value)
|
||||
assert res.status == "Accepted"
|
||||
|
||||
event_data_subscription_mock = Mock()
|
||||
_env.probe_module.subscribe_variable(
|
||||
"ocpp", "event_data", event_data_subscription_mock
|
||||
)
|
||||
|
||||
await change_var("HeartbeatInterval", "1")
|
||||
|
||||
# assert no event before monitoring is enabled
|
||||
await asyncio.sleep(0.1)
|
||||
event_data_subscription_mock.assert_not_called()
|
||||
|
||||
# enable monitoring
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"monitor_variables",
|
||||
{
|
||||
"component_variables": [
|
||||
{
|
||||
"component": {"name": "IGNORED"},
|
||||
"variable": {"name": "HeartbeatInterval"},
|
||||
},
|
||||
{
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "MeterValuesAlignedData"},
|
||||
},
|
||||
{
|
||||
"component": {"name": ""},
|
||||
"variable": {"name": "UNKNOWN"},
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
assert res is None
|
||||
|
||||
# verify event is triggered
|
||||
await change_var("HeartbeatInterval", "42")
|
||||
await wait_for_mock_called(
|
||||
event_data_subscription_mock,
|
||||
mock_call(
|
||||
{
|
||||
"actual_value": "42",
|
||||
"component_variable": {
|
||||
"component": {"name": "IGNORED"},
|
||||
"variable": {"name": "HeartbeatInterval"},
|
||||
},
|
||||
"event_id": ANY,
|
||||
"event_notification_type": "CustomMonitor",
|
||||
"timestamp": ANY,
|
||||
"trigger": "Alerting",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
async def test_subscribe_charging_schedules(self, _env):
|
||||
subscription_mock = Mock()
|
||||
_env.probe_module.subscribe_variable(
|
||||
"ocpp", "charging_schedules", subscription_mock
|
||||
)
|
||||
|
||||
await _env.charge_point.set_charging_profile_req(
|
||||
SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles={
|
||||
"chargingProfileId": 0,
|
||||
"stackLevel": 1,
|
||||
"chargingProfilePurpose": "TxDefaultProfile",
|
||||
"chargingProfileKind": "Relative",
|
||||
"chargingSchedule": {
|
||||
"chargingRateUnit": "A",
|
||||
"chargingSchedulePeriod": [{"limit": 32.0, "startPeriod": 0}],
|
||||
},
|
||||
},
|
||||
)
|
||||
)
|
||||
await wait_for_mock_called(
|
||||
subscription_mock,
|
||||
mock_call(
|
||||
{
|
||||
"schedules": [
|
||||
{
|
||||
"charging_rate_unit": "A",
|
||||
"charging_schedule_period": [
|
||||
{
|
||||
"limit": 64,
|
||||
"stack_level": 0,
|
||||
"start_period": 0,
|
||||
}
|
||||
],
|
||||
"evse": 0,
|
||||
"duration": ANY,
|
||||
"start_schedule": ANY,
|
||||
},
|
||||
{
|
||||
"charging_rate_unit": "A",
|
||||
"charging_schedule_period": [
|
||||
{
|
||||
"limit": 32,
|
||||
"stack_level": 1,
|
||||
"start_period": 0,
|
||||
}
|
||||
],
|
||||
"evse": 1,
|
||||
"duration": ANY,
|
||||
"start_schedule": ANY,
|
||||
},
|
||||
{
|
||||
"charging_rate_unit": "A",
|
||||
"charging_schedule_period": [
|
||||
{
|
||||
"limit": 32,
|
||||
"stack_level": 1,
|
||||
"start_period": 0,
|
||||
}
|
||||
],
|
||||
"evse": 2,
|
||||
"duration": ANY,
|
||||
"start_schedule": ANY,
|
||||
},
|
||||
]
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
async def test_subscribe_is_connected(self, _env):
|
||||
subscription_mock = Mock()
|
||||
_env.probe_module.subscribe_variable(
|
||||
"ocpp", "is_connected", subscription_mock)
|
||||
|
||||
assert await _env.probe_module.call_command("ocpp", "stop", None)
|
||||
assert await _env.probe_module.call_command("ocpp", "restart", None)
|
||||
|
||||
await wait_for_mock_called(subscription_mock, mock_call(False))
|
||||
await wait_for_mock_called(subscription_mock, mock_call(True))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"overwrite_implementation",
|
||||
[{"security": {"update_leaf_certificate": "InvalidSignature"}}],
|
||||
)
|
||||
async def test_subscribe_security_event(self, _env):
|
||||
subscription_mock = Mock()
|
||||
_env.probe_module.subscribe_variable(
|
||||
"ocpp", "security_event", subscription_mock
|
||||
)
|
||||
# trigger security event by invalid certificate signed request
|
||||
await _env.charge_point.certificate_signed_req(certificate_chain="somechain")
|
||||
|
||||
await wait_for_mock_called(
|
||||
subscription_mock,
|
||||
mock_call(
|
||||
{"info": "InvalidSignature", "type": "InvalidChargePointCertificate"}
|
||||
),
|
||||
)
|
||||
|
||||
async def test_change_availability_request_connector(self, _env):
|
||||
_env.probe_module_command_mocks["evse_manager"]["enable_disable"].reset_mock(
|
||||
)
|
||||
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"change_availability",
|
||||
{
|
||||
"request": {
|
||||
"operational_status": "Inoperative",
|
||||
"evse": {
|
||||
"id": 1,
|
||||
"connector_id": 1,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
assert res == {"status": "Accepted"}
|
||||
|
||||
await wait_for_mock_called(
|
||||
_env.probe_module_command_mocks["evse_manager"]["enable_disable"],
|
||||
call=mock_call(
|
||||
{
|
||||
"cmd_source": {
|
||||
"enable_priority": 5000,
|
||||
"enable_source": "CSMS",
|
||||
"enable_state": "Disable",
|
||||
},
|
||||
"connector_id": 0,
|
||||
}
|
||||
),
|
||||
) # as currently implemented in disable_evse callback in OCPP module
|
||||
|
||||
_env.probe_module_command_mocks["evse_manager"]["enable_disable"].reset_mock(
|
||||
)
|
||||
_env.probe_module_command_mocks["evse_manager_b"]["enable_disable"].reset_mock(
|
||||
)
|
||||
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"change_availability",
|
||||
{
|
||||
"request": {
|
||||
"operational_status": "Inoperative",
|
||||
"evse": {
|
||||
"id": 2,
|
||||
"connector_id": 1,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
assert res == {"status": "Accepted"}
|
||||
|
||||
await wait_for_mock_called(
|
||||
_env.probe_module_command_mocks["evse_manager_b"]["enable_disable"],
|
||||
call=mock_call(
|
||||
{
|
||||
"cmd_source": {
|
||||
"enable_priority": 5000,
|
||||
"enable_source": "CSMS",
|
||||
"enable_state": "Disable",
|
||||
},
|
||||
"connector_id": 0,
|
||||
}
|
||||
),
|
||||
) # as currently implemented in disable_evse callback in OCPP module
|
||||
|
||||
async def test_change_availability_request_evse(self, _env):
|
||||
_env.probe_module_command_mocks["evse_manager"]["enable_disable"].reset_mock(
|
||||
)
|
||||
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"change_availability",
|
||||
{"request": {"operational_status": "Inoperative"}},
|
||||
)
|
||||
assert res == {"status": "Accepted"}
|
||||
await wait_for_mock_called(
|
||||
_env.probe_module_command_mocks["evse_manager"]["enable_disable"],
|
||||
call=mock_call(
|
||||
{
|
||||
"cmd_source": {
|
||||
"enable_priority": 5000,
|
||||
"enable_source": "CSMS",
|
||||
"enable_state": "Disable",
|
||||
},
|
||||
"connector_id": 0,
|
||||
}
|
||||
),
|
||||
)
|
||||
assert (
|
||||
len(
|
||||
_env.probe_module_command_mocks["evse_manager"][
|
||||
"enable_disable"
|
||||
].mock_calls
|
||||
)
|
||||
== 1
|
||||
)
|
||||
|
||||
async def test_change_availability_request_failed(self, _env):
|
||||
# Failed request: no connector id
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"change_availability",
|
||||
{
|
||||
"request": {
|
||||
"operational_status": "Inoperative",
|
||||
"evse": {
|
||||
"id": 1,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
assert res == {
|
||||
"status": "Rejected",
|
||||
"status_info": {
|
||||
"additional_info": ANY, # No connector id specified;
|
||||
"reason_code": "InvalidInput",
|
||||
},
|
||||
}
|
||||
|
||||
res = await _env.probe_module.call_command(
|
||||
"ocpp",
|
||||
"change_availability",
|
||||
{
|
||||
"request": {
|
||||
"operational_status": "Inoperative",
|
||||
"evse": {"id": 2, "connector_id": 2},
|
||||
}
|
||||
},
|
||||
)
|
||||
assert res == {
|
||||
"status": "Rejected",
|
||||
"status_info": {
|
||||
"additional_info": ANY, # Invalid connector id specified
|
||||
"reason_code": "InvalidInput",
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,555 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from ocpp.v16.enums import *
|
||||
from ocpp.v16.datatypes import *
|
||||
from ocpp.v16 import call, call_result
|
||||
|
||||
|
||||
def abs_req1_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.charge_point_max_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=86400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=10, number_phases=3)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req2_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=6, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=60, limit=10, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=120, limit=8, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=180, limit=25, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=260, limit=8, number_phases=3),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req2_test_con0():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.charge_point_max_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=86400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=20)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req3_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=3,
|
||||
transaction_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=240,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=8),
|
||||
ChargingSchedulePeriod(start_period=50, limit=11),
|
||||
ChargingSchedulePeriod(start_period=140, limit=16),
|
||||
ChargingSchedulePeriod(start_period=200, limit=6),
|
||||
ChargingSchedulePeriod(start_period=240, limit=12),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test1(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=8, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=50 - passed_seconds, limit=10, number_phases=3
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200 - passed_seconds, limit=6, number_phases=3
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=240 - passed_seconds, limit=10, number_phases=3
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=260 - passed_seconds, limit=8, number_phases=3
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=300 - passed_seconds, limit=10, number_phases=3
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req1_test2():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=6, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=60, limit=10, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=120, limit=8, number_phases=3),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test2(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=6, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=60 - passed_seconds, limit=10, number_phases=3
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=120 - passed_seconds, limit=8, number_phases=3
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req1_test3():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=3,
|
||||
transaction_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=260,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=6),
|
||||
ChargingSchedulePeriod(start_period=60, limit=10),
|
||||
ChargingSchedulePeriod(start_period=120, limit=8),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test3(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=6),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=60 - passed_seconds, limit=10
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=120 - passed_seconds, limit=8
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=260 - passed_seconds, limit=48
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req1_test5():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=6),
|
||||
ChargingSchedulePeriod(start_period=50, limit=5),
|
||||
ChargingSchedulePeriod(start_period=100, limit=8),
|
||||
ChargingSchedulePeriod(start_period=200, limit=10),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req2_test5():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=1,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=150,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=7),
|
||||
ChargingSchedulePeriod(start_period=100, limit=9),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test5_1(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=350,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=7),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100 - passed_seconds, limit=9
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=150 - passed_seconds, limit=8
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200 - passed_seconds, limit=10
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test5_2(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=550,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=7),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100 - passed_seconds, limit=9
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=150 - passed_seconds, limit=8
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200 - passed_seconds, limit=10
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=400 - passed_seconds, limit=48
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req1_test6():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.charge_point_max_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=800,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=18),
|
||||
ChargingSchedulePeriod(start_period=50, limit=24),
|
||||
ChargingSchedulePeriod(start_period=100, limit=14),
|
||||
ChargingSchedulePeriod(start_period=200, limit=24),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req2_test6():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=1,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=16),
|
||||
ChargingSchedulePeriod(start_period=100, limit=20),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_req3_test6():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=2,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=3,
|
||||
stack_level=1,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=500,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10),
|
||||
ChargingSchedulePeriod(start_period=100, limit=16),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test6_con0():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=0,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=700,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=18),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=50, limit=24),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100, limit=14),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200, limit=24),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test6_con1(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=900,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=16),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100 - passed_seconds, limit=14
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200 - passed_seconds, limit=20
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=400 - passed_seconds, limit=24
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=800 - passed_seconds, limit=48
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test6_con2(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=2,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=10),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100 - passed_seconds, limit=14
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200 - passed_seconds, limit=16
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test1_con0(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=10)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test2_con0(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=20)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def abs_exp_test3_con0(passed_seconds):
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=90,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=48)
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from ocpp.v16.enums import *
|
||||
from ocpp.v16.datatypes import *
|
||||
from ocpp.v16 import call, call_result
|
||||
|
||||
|
||||
def comb_req1_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.charge_point_max_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=200,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10), # 6900
|
||||
ChargingSchedulePeriod(
|
||||
start_period=80, limit=20, number_phases=1 # 4600
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100, limit=20, number_phases=3 # 13800
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def comb_req2_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.watts,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=11000, number_phases=3
|
||||
),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=60, limit=6900, number_phases=1
|
||||
),
|
||||
ChargingSchedulePeriod(start_period=120, limit=5520),
|
||||
ChargingSchedulePeriod(start_period=180, limit=17250),
|
||||
ChargingSchedulePeriod(start_period=260, limit=5520),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def comb_exp1_test1():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.watts,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=6900, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=80, limit=4600, number_phases=1),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100, limit=6900, number_phases=1),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=120, limit=5520, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=180, limit=13800, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200, limit=17250, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=260, limit=5520, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=300, limit=33120, number_phases=3),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def comb_exp2_test1():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=10, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=80, limit=20, number_phases=1),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100, limit=30, number_phases=1),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=120, limit=8, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=180, limit=20, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200, limit=25, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=260, limit=8, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=300, limit=48, number_phases=3),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def comb_req1_test2():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.charge_point_max_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
charging_schedule=ChargingSchedule(
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=80, limit=20, number_phases=2),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=160, limit=20, number_phases=3),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def comb_exp_test2():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=0,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=80, limit=20, number_phases=2),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=160, limit=20, number_phases=3),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,172 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from ocpp.v16.enums import *
|
||||
from ocpp.v16.datatypes import *
|
||||
from ocpp.v16 import call, call_result
|
||||
|
||||
|
||||
def rec_req1_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.recurring,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=16),
|
||||
ChargingSchedulePeriod(start_period=100, limit=20),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rec_req1_test2():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.recurring,
|
||||
recurrency_kind=RecurrencyKind.daily,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=14),
|
||||
ChargingSchedulePeriod(start_period=5000, limit=16),
|
||||
ChargingSchedulePeriod(start_period=15000, limit=20),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rec_req2_test2():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=1,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.recurring,
|
||||
recurrency_kind=RecurrencyKind.daily,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
duration=86400,
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10),
|
||||
ChargingSchedulePeriod(start_period=10000, limit=22),
|
||||
ChargingSchedulePeriod(start_period=20000, limit=6),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rec_exp_test2():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=172800,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10),
|
||||
ChargingSchedulePeriod(start_period=10000, limit=22),
|
||||
ChargingSchedulePeriod(start_period=20000, limit=6),
|
||||
ChargingSchedulePeriod(start_period=86400, limit=10),
|
||||
ChargingSchedulePeriod(start_period=96400, limit=22),
|
||||
ChargingSchedulePeriod(start_period=106400, limit=6),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rec_req1_test3():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.recurring,
|
||||
recurrency_kind=RecurrencyKind.weekly,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=14),
|
||||
ChargingSchedulePeriod(start_period=5000, limit=16),
|
||||
ChargingSchedulePeriod(start_period=15000, limit=20),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rec_req2_test3():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=1,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.recurring,
|
||||
recurrency_kind=RecurrencyKind.weekly,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
duration=86400,
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10),
|
||||
ChargingSchedulePeriod(start_period=10000, limit=22),
|
||||
ChargingSchedulePeriod(start_period=20000, limit=6),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rec_exp_test3():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=172800,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10),
|
||||
ChargingSchedulePeriod(start_period=10000, limit=22),
|
||||
ChargingSchedulePeriod(start_period=20000, limit=6),
|
||||
ChargingSchedulePeriod(start_period=86400, limit=20),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,183 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from ocpp.v16.enums import *
|
||||
from ocpp.v16.datatypes import *
|
||||
from ocpp.v16 import call, call_result
|
||||
|
||||
|
||||
def rel_req1_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
transaction_id=1,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.relative,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=16, number_phases=1),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=50, limit=20, number_phases=3),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rel_exp_test1():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=16, number_phases=1),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=50, limit=20, number_phases=3),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rel_req1_test2():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.relative,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=16, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=100, limit=20, number_phases=3),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rel_req2_test2():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=1,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.relative,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=200,
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=10, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=50, limit=6, number_phases=3),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rel_exp_test2():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=10, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=50, limit=6, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=200, limit=20, number_phases=3),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rel_req1_test3():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=1,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.relative,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
charging_rate_unit=ChargingRateUnitType.watts,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=11000),
|
||||
ChargingSchedulePeriod(start_period=90, limit=22000),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rel_req2_test3():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=2,
|
||||
stack_level=2,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.relative,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=32),
|
||||
ChargingSchedulePeriod(start_period=6, limit=20),
|
||||
ChargingSchedulePeriod(start_period=12, limit=8),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def rel_exp_test3():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=90,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=32),
|
||||
ChargingSchedulePeriod(start_period=6, limit=20),
|
||||
ChargingSchedulePeriod(start_period=12, limit=8),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from ocpp.v16.enums import *
|
||||
from ocpp.v16.datatypes import *
|
||||
from ocpp.v16 import call, call_result
|
||||
|
||||
|
||||
def unp_req1_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=0,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.charge_point_max_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=86400,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(start_period=0, limit=10)
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def unp_req2_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_default_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0,
|
||||
limit=6,
|
||||
),
|
||||
ChargingSchedulePeriod(start_period=60, limit=10),
|
||||
ChargingSchedulePeriod(start_period=120, limit=8),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def unp_req3_test1():
|
||||
return call.SetChargingProfile(
|
||||
connector_id=1,
|
||||
cs_charging_profiles=ChargingProfile(
|
||||
charging_profile_id=1,
|
||||
stack_level=0,
|
||||
charging_profile_purpose=ChargingProfilePurposeType.tx_profile,
|
||||
charging_profile_kind=ChargingProfileKindType.absolute,
|
||||
valid_from=datetime.now(timezone.utc).isoformat(),
|
||||
valid_to=(datetime.now(timezone.utc) +
|
||||
timedelta(days=3)).isoformat(),
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0,
|
||||
limit=12,
|
||||
),
|
||||
ChargingSchedulePeriod(start_period=60, limit=10),
|
||||
ChargingSchedulePeriod(start_period=120, limit=6),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def unp_exp_test1():
|
||||
return call_result.GetCompositeSchedule(
|
||||
status=GetCompositeScheduleStatus.accepted,
|
||||
schedule_start=datetime.now(timezone.utc).isoformat(),
|
||||
connector_id=1,
|
||||
charging_schedule=ChargingSchedule(
|
||||
duration=300,
|
||||
start_schedule=datetime.now(timezone.utc).isoformat(),
|
||||
charging_rate_unit=ChargingRateUnitType.amps,
|
||||
charging_schedule_period=[
|
||||
ChargingSchedulePeriod(
|
||||
start_period=0, limit=12, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=60, limit=10, number_phases=3),
|
||||
ChargingSchedulePeriod(
|
||||
start_period=120, limit=6, number_phases=3),
|
||||
],
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,934 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
import pytest
|
||||
import asyncio
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from everest.testing.core_utils.controller.test_controller_interface import (
|
||||
TestController,
|
||||
)
|
||||
from ocpp.v16.enums import (
|
||||
ChargingRateUnitType,
|
||||
)
|
||||
|
||||
from ocpp.v16.enums import ChargingProfileStatus, RemoteStartStopStatus, ClearChargingProfileStatus
|
||||
|
||||
from ocpp.v16 import call, call_result
|
||||
|
||||
# fmt: off
|
||||
from validations import (validate_composite_schedule,
|
||||
validate_remote_start_stop_transaction,
|
||||
validate_standard_start_transaction)
|
||||
|
||||
from everest.testing.ocpp_utils.charge_point_utils import wait_for_and_validate, TestUtility
|
||||
from everest.testing.ocpp_utils.fixtures import charge_point_v16
|
||||
from everest.testing.ocpp_utils.central_system import CentralSystem
|
||||
from everest.testing.ocpp_utils.charge_point_v16 import ChargePoint16
|
||||
|
||||
from smart_charging_profiles.absolute_profiles import *
|
||||
from smart_charging_profiles.relative_profiles import *
|
||||
from smart_charging_profiles.recurring_profiles import *
|
||||
from smart_charging_profiles.combined_profiles import *
|
||||
from everest_test_utils import *
|
||||
# fmt: on
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_absolute_1(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
test_controller.plug_in(connector_id=1)
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
req1 = abs_req1_test1()
|
||||
req2 = abs_req2_test1()
|
||||
req3 = abs_req3_test1()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req3) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=400)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req1.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test1(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_absolute_2(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
req = abs_req1_test2()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req) == exp_scp_result
|
||||
|
||||
test_controller.plug_in(connector_id=1)
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=300)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test2(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_absolute_3(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
test_controller.plug_in(connector_id=1)
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
req = abs_req1_test3()
|
||||
|
||||
await asyncio.sleep(1) #ensure we respond before sending
|
||||
assert await charge_point_v16.set_charging_profile_req(req) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=300)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test3(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
test_controller.plug_out()
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_absolute_4(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
test_controller.plug_in(connector_id=1)
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
req = abs_req1_test2()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=300)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test2(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_absolute_5(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
req1 = abs_req1_test5()
|
||||
req2 = abs_req2_test5()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
|
||||
test_controller.plug_in(connector_id=1)
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=350)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req1.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test5_1(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=550)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req1.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test5_2(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.everest_core_config(
|
||||
get_everest_config_path_str("everest-config-two-connectors.yaml")
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_absolute_6(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
req1 = abs_req1_test6()
|
||||
req2 = abs_req2_test6()
|
||||
req3 = abs_req3_test6()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req3) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=0, duration=700)
|
||||
)
|
||||
|
||||
exp_con0 = abs_exp_test6_con0()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp_con0,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=0, duration=700)
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp_con0,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
test_controller.plug_in(connector_id=1)
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=900)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req1.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
|
||||
exp_con1 = abs_exp_test6_con1(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp_con1,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
test_utility.messages.clear()
|
||||
|
||||
test_controller.plug_in(connector_id=2)
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_2, connector_id=2
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
2, test_config.authorization_info.valid_id_tag_2, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=2, duration=400)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req2.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
|
||||
exp_con2 = abs_exp_test6_con2(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp_con2,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skip(
|
||||
"Expected behavior when schedules are sent in other unit than composite schedules are requested needs to be discussed."
|
||||
)
|
||||
async def test_combined_1(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
|
||||
test_controller.plug_in()
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
req1 = comb_req1_test1()
|
||||
req2 = comb_req2_test1()
|
||||
exp1 = comb_exp1_test1()
|
||||
exp2 = comb_exp2_test1()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(
|
||||
connector_id=1, duration=400, charging_rate_unit=ChargingRateUnitType.watts
|
||||
)
|
||||
)
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(
|
||||
connector_id=1, duration=400, charging_rate_unit=ChargingRateUnitType.amps
|
||||
)
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp1,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp2,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_combined_2(charge_point_v16: ChargePoint16, test_utility: TestUtility):
|
||||
|
||||
req = comb_req1_test2()
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=0, duration=400)
|
||||
)
|
||||
|
||||
exp = comb_exp_test2()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recurring_1(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
|
||||
test_controller.plug_in()
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
req = rec_req1_test1()
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.rejected
|
||||
)
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req) == exp_scp_result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recurring_2(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
|
||||
test_controller.plug_in()
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
req1 = rec_req1_test2()
|
||||
req2 = rec_req2_test2()
|
||||
exp = rec_exp_test2()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=172800)
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recurring_3(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
|
||||
test_controller.plug_in()
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
req1 = rec_req1_test3()
|
||||
req2 = rec_req2_test3()
|
||||
exp = rec_exp_test3()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=172800)
|
||||
)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_relative_1(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
|
||||
req1 = rel_req1_test1()
|
||||
|
||||
test_controller.plug_in()
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
await asyncio.sleep(1) #enseure we respond befor sending next message
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=400)
|
||||
)
|
||||
|
||||
exp = rel_exp_test1()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_relative_2(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
|
||||
req1 = rel_req1_test2()
|
||||
req2 = rel_req2_test2()
|
||||
|
||||
test_controller.plug_in()
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=300)
|
||||
)
|
||||
|
||||
exp = rel_exp_test2()
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_relative_3(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
):
|
||||
|
||||
req1 = rel_req1_test3()
|
||||
req2 = rel_req2_test3()
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
|
||||
# start charging session
|
||||
test_controller.plug_in()
|
||||
|
||||
# send RemoteStartTransaction.req
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"RemoteStartTransaction",
|
||||
call_result.RemoteStartTransaction(
|
||||
RemoteStartStopStatus.accepted),
|
||||
validate_remote_start_stop_transaction,
|
||||
)
|
||||
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=90)
|
||||
)
|
||||
|
||||
exp = rel_exp_test3()
|
||||
|
||||
# expect correct GetCompositeSchedule.conf
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_charging_profiles_for_connector_0(
|
||||
test_config,
|
||||
charge_point_v16: ChargePoint16,
|
||||
test_controller: TestController,
|
||||
test_utility: TestUtility,
|
||||
central_system_v16: CentralSystem,
|
||||
):
|
||||
|
||||
exp_scp_result = call_result.SetChargingProfile(
|
||||
ChargingProfileStatus.accepted
|
||||
)
|
||||
|
||||
test_controller.plug_in(connector_id=1)
|
||||
await charge_point_v16.remote_start_transaction_req(
|
||||
id_tag=test_config.authorization_info.valid_id_tag_1, connector_id=1
|
||||
)
|
||||
# expect StartTransaction.req
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"StartTransaction",
|
||||
call.StartTransaction(
|
||||
1, test_config.authorization_info.valid_id_tag_1, 0, ""
|
||||
),
|
||||
validate_standard_start_transaction,
|
||||
)
|
||||
|
||||
req1 = abs_req1_test1()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req1) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=400)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req1.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test1_con0(passed_seconds)
|
||||
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
req2 = abs_req2_test_con0()
|
||||
|
||||
assert await charge_point_v16.set_charging_profile_req(req2) == exp_scp_result
|
||||
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=400)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req2.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test2_con0(passed_seconds)
|
||||
# expect correct GetCompositeSchedule.conf
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
|
||||
await charge_point_v16.clear_charging_profile_req(id=2)
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"ClearChargingProfile",
|
||||
call_result.ClearChargingProfile(
|
||||
ClearChargingProfileStatus.accepted),
|
||||
)
|
||||
|
||||
test_controller.stop()
|
||||
await asyncio.sleep(2)
|
||||
test_controller.start()
|
||||
|
||||
charge_point_v16 = await central_system_v16.wait_for_chargepoint()
|
||||
await charge_point_v16.get_composite_schedule(
|
||||
call.GetCompositeSchedule(connector_id=1, duration=90)
|
||||
)
|
||||
|
||||
passed_seconds = int(
|
||||
(
|
||||
datetime.now(timezone.utc)
|
||||
- datetime.fromisoformat(req1.cs_charging_profiles.valid_from)
|
||||
).total_seconds()
|
||||
)
|
||||
exp = abs_exp_test3_con0(passed_seconds)
|
||||
|
||||
# expect correct GetCompositeSchedule.conf
|
||||
assert await wait_for_and_validate(
|
||||
test_utility,
|
||||
charge_point_v16,
|
||||
"GetCompositeSchedule",
|
||||
exp,
|
||||
validate_composite_schedule,
|
||||
)
|
||||
Reference in New Issue
Block a user