- 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
487 lines
20 KiB
Python
487 lines
20 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# Copyright Pionix GmbH and Contributors to EVerest
|
|
|
|
import pytest
|
|
from datetime import datetime, timezone
|
|
|
|
import traceback
|
|
# fmt: off
|
|
import logging
|
|
|
|
from everest.testing.core_utils.controller.test_controller_interface import TestController
|
|
|
|
from ocpp.v21 import call as call21
|
|
from ocpp.v21 import call_result as call_result21
|
|
from ocpp.v21.enums import *
|
|
from ocpp.v21.datatypes import *
|
|
from ocpp.routing import on, create_route_map
|
|
from everest.testing.ocpp_utils.fixtures import *
|
|
from everest_test_utils import * # Needs to be before the datatypes below since it overrides the v21 Action enum with the v16 one
|
|
from ocpp.v21.enums import (Action, ConnectorStatusEnumType, AuthorizationStatusEnumType, EnergyTransferModeEnumType, AttributeEnumType, GetVariableStatusEnumType, NotifyEVChargingNeedsStatusEnumType, NotifyAllowedEnergyTransferStatusEnumType)
|
|
from validations import validate_status_notification_201
|
|
from everest.testing.core_utils._configuration.libocpp_configuration_helper import GenericOCPP2XConfigAdjustment
|
|
from everest.testing.ocpp_utils.charge_point_utils import wait_for_and_validate, TestUtility
|
|
# fmt: on
|
|
|
|
log = logging.getLogger("bidirectionalTest")
|
|
|
|
|
|
def validate_notify_ev_charging_needs(meta_data, msg, expected):
|
|
# Q01.FR.03
|
|
return (
|
|
msg.payload["evseId"] == expected["evseId"]
|
|
and msg.payload["chargingNeeds"]["requestedEnergyTransfer"] == expected["requestedEnergyTransfer"]
|
|
and msg.payload["chargingNeeds"]["v2xChargingParameters"]
|
|
and msg.payload["chargingNeeds"]["controlMode"] == expected["controlMode"]
|
|
)
|
|
|
|
|
|
def validate_tx_event_with_evccid(meta_data, msg, expected):
|
|
return (
|
|
msg.payload["eventType"] == expected["eventType"]
|
|
and msg.payload["idToken"]["additionalInfo"][0]["type"] == "EVCCID"
|
|
)
|
|
|
|
|
|
@pytest.mark.xdist_group(name="ISO15118")
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config("everest-config-ocpp201-sil-dc-d20-eim.yaml")
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
)
|
|
]
|
|
)
|
|
)
|
|
async def test_q01(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""
|
|
Q01
|
|
...
|
|
"""
|
|
|
|
log.info(
|
|
"##################### Q01: V2X Authorization #################"
|
|
)
|
|
id_token = IdTokenType(id_token="8BADF00D",
|
|
type="ISO14443")
|
|
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint(wait_for_bootnotification=True)
|
|
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"StatusNotification",
|
|
call21.StatusNotification(
|
|
1, ConnectorStatusEnumType.available, 1, datetime.now().isoformat()
|
|
),
|
|
validate_status_notification_201,
|
|
)
|
|
|
|
iso_enabled = GetVariableDataType(component=ComponentType(name="ISO15118Ctrlr", evse=EVSEType(id=1)),
|
|
variable=VariableType(name="Enabled"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
v2x_enabled = GetVariableDataType(component=ComponentType(name="V2XChargingCtrlr", evse=EVSEType(id=1)),
|
|
variable=VariableType(name="Enabled"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
v2x_supported_op_modes = GetVariableDataType(component=ComponentType(name="V2XChargingCtrlr", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="SupportedOperationModes"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
v2x_supported_energy_transfers = GetVariableDataType(component=ComponentType(name="V2XChargingCtrlr", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="SupportedEnergyTransferModes"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
list_of_vars = [iso_enabled, v2x_enabled,
|
|
v2x_supported_op_modes, v2x_supported_energy_transfers]
|
|
r: call_result21.GetVariables = await charge_point_v21.get_variables_req(get_variable_data=list_of_vars)
|
|
|
|
for result in r.get_variable_result:
|
|
assert result['attribute_status'] == GetVariableStatusEnumType.accepted
|
|
if result['variable']['name'] == 'Enabled':
|
|
# Q01.FR.01
|
|
assert result['attribute_value'] == 'true'
|
|
elif result['variable']['name'] == 'SupportedOperationModes':
|
|
# Q01.FR.31
|
|
# TODO(mlitre) update to check for the min requirements once they are supported
|
|
# Notably we need: ChargingOnly, CentralSetpoint and CentralFrequency
|
|
assert result['attribute_value']
|
|
elif result['variable']['name'] == 'SupportedEnergyTransferModes':
|
|
# Q01.FR.32, we just check that it is not empty
|
|
assert result['attribute_value']
|
|
|
|
test_controller.plug_in_dc_iso()
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"StatusNotification",
|
|
call21.StatusNotification(
|
|
1, ConnectorStatusEnumType.occupied, 1, datetime.now().isoformat()
|
|
),
|
|
validate_status_notification_201,
|
|
)
|
|
|
|
@on(Action.authorize)
|
|
def on_authorize(**kwargs):
|
|
return call_result21.Authorize(
|
|
id_token_info=IdTokenInfoType(
|
|
status=AuthorizationStatusEnumType.accepted,
|
|
group_id_token=IdTokenType(
|
|
id_token="12345", type="Central"
|
|
),
|
|
),
|
|
allowed_energy_transfer=[EnergyTransferModeEnumType.dc_bpt]
|
|
)
|
|
|
|
setattr(charge_point_v21, "on_authorize", on_authorize)
|
|
central_system_v21.chargepoint.route_map = create_route_map(
|
|
central_system_v21.chargepoint
|
|
)
|
|
test_controller.swipe(id_token.id_token)
|
|
|
|
# Question: should we check other flows aka auth first then plug
|
|
# Checking here Q01.FR.02 for idToken is not reliable, as we start tx before we get evcc id
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"TransactionEvent",
|
|
{"eventType": "Started", "offline": False},
|
|
)
|
|
|
|
# Q01.FR.03: Check all the fields of NotifyEVChargingNeeds
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"NotifyEVChargingNeeds",
|
|
{"evseId": 1, "requestedEnergyTransfer": "DC",
|
|
"controlMode": "DynamicControl", "mobilityNeedsMode": "EVCC"},
|
|
validate_notify_ev_charging_needs
|
|
)
|
|
|
|
# Check variables after NotifyEVChargingNeeds, so that we don't have to guess when the variables have been updated
|
|
ev_available = GetVariableDataType(component=ComponentType(name="ConnectedEV", evse=EVSEType(id=1)),
|
|
variable=VariableType(name="Available"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
ev_vehicle_id = GetVariableDataType(component=ComponentType(name="ConnectedEV", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="VehicleId"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
ev_protocol_agreed = GetVariableDataType(component=ComponentType(name="ConnectedEV", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="ProtocolAgreed"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
ev_protocol_supported_by_ev1 = GetVariableDataType(component=ComponentType(name="ConnectedEV", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="ProtocolSupportedByEV", instance="1"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
list_of_vars = [ev_available, ev_vehicle_id,
|
|
ev_protocol_agreed, ev_protocol_supported_by_ev1]
|
|
r: call_result21.GetVariables = await charge_point_v21.get_variables_req(get_variable_data=list_of_vars)
|
|
# TODO(mlitre): Add check on VehicleCertificate when it is supported
|
|
|
|
# Q01.FR.36: Validate ConnectedEV variables
|
|
for result in r.get_variable_result:
|
|
assert result['attribute_status'] == GetVariableStatusEnumType.accepted
|
|
if result['variable']['name'] == 'Available':
|
|
assert result['attribute_value'] == 'true'
|
|
elif result['variable']['name'] == 'VehicleId':
|
|
assert result['attribute_value']
|
|
elif result['variable']['name'] == 'ProtocolAgreed':
|
|
assert result['attribute_value'] == 'urn:iso:std:iso:15118:-20:DC,1,0'
|
|
elif result['variable']['name'] == 'ProtocolSupportedByEV':
|
|
# TODO(mlitre): How many should we check?
|
|
assert result['attribute_value']
|
|
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"TransactionEvent",
|
|
{"eventType": "Updated", "transactionInfo": {"chargingState": "Charging"}},
|
|
)
|
|
|
|
test_controller.swipe(id_token.id_token)
|
|
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"TransactionEvent",
|
|
{"eventType": "Ended"},
|
|
validate_tx_event_with_evccid
|
|
)
|
|
|
|
|
|
@pytest.mark.xdist_group(name="ISO15118")
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config("everest-config-ocpp201-sil-dc-d20-eim.yaml")
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
)
|
|
]
|
|
)
|
|
)
|
|
async def test_rejected_q01(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""
|
|
Q01
|
|
...
|
|
"""
|
|
|
|
log.info(
|
|
"##################### Q01: V2X Authorization #################"
|
|
)
|
|
id_token = IdTokenType(id_token="8BADF00D",
|
|
type="ISO14443")
|
|
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint(wait_for_bootnotification=True)
|
|
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"StatusNotification",
|
|
call21.StatusNotification(
|
|
1, ConnectorStatusEnumType.available, 1, datetime.now().isoformat()
|
|
),
|
|
validate_status_notification_201,
|
|
)
|
|
|
|
iso_enabled = GetVariableDataType(component=ComponentType(name="ISO15118Ctrlr", evse=EVSEType(id=1)),
|
|
variable=VariableType(name="Enabled"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
v2x_enabled = GetVariableDataType(component=ComponentType(name="V2XChargingCtrlr", evse=EVSEType(id=1)),
|
|
variable=VariableType(name="Enabled"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
v2x_supported_op_modes = GetVariableDataType(component=ComponentType(name="V2XChargingCtrlr", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="SupportedOperationModes"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
v2x_supported_energy_transfers = GetVariableDataType(component=ComponentType(name="V2XChargingCtrlr", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="SupportedEnergyTransferModes"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
list_of_vars = [iso_enabled, v2x_enabled,
|
|
v2x_supported_op_modes, v2x_supported_energy_transfers]
|
|
r: call_result21.GetVariables = await charge_point_v21.get_variables_req(get_variable_data=list_of_vars)
|
|
|
|
for result in r.get_variable_result:
|
|
assert result['attribute_status'] == GetVariableStatusEnumType.accepted
|
|
if result['variable']['name'] == 'Enabled':
|
|
# Q01.FR.01
|
|
assert result['attribute_value'] == 'true'
|
|
elif result['variable']['name'] == 'SupportedOperationModes':
|
|
# Q01.FR.31
|
|
# TODO(mlitre) update to check for the min requirements once they are supported: ChargingOnly, CentralSetpoint, CentralFrequency
|
|
assert result['attribute_value']
|
|
elif result['variable']['name'] == 'SupportedEnergyTransferModes':
|
|
# Q01.FR.32, we just check that it is not empty
|
|
assert result['attribute_value']
|
|
|
|
test_controller.plug_in_dc_iso()
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"StatusNotification",
|
|
call21.StatusNotification(
|
|
1, ConnectorStatusEnumType.occupied, 1, datetime.now().isoformat()
|
|
),
|
|
validate_status_notification_201,
|
|
)
|
|
|
|
@on(Action.authorize)
|
|
def on_authorize(**kwargs):
|
|
return call_result21.Authorize(
|
|
id_token_info=IdTokenInfoType(
|
|
status=AuthorizationStatusEnumType.accepted,
|
|
group_id_token=IdTokenType(
|
|
id_token="12345", type="Central"
|
|
),
|
|
),
|
|
allowed_energy_transfer=[EnergyTransferModeEnumType.dc_bpt]
|
|
)
|
|
|
|
setattr(charge_point_v21, "on_authorize", on_authorize)
|
|
central_system_v21.chargepoint.route_map = create_route_map(
|
|
central_system_v21.chargepoint
|
|
)
|
|
test_controller.swipe(id_token.id_token)
|
|
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"TransactionEvent",
|
|
{"eventType": "Started", "offline": False},
|
|
)
|
|
|
|
@on(Action.notify_ev_charging_needs)
|
|
def on_notify_ev_charging_needs(**kwargs):
|
|
return call_result21.NotifyEVChargingNeeds(
|
|
status=NotifyEVChargingNeedsStatusEnumType.rejected
|
|
)
|
|
|
|
setattr(charge_point_v21, "on_notify_ev_charging_needs",
|
|
on_notify_ev_charging_needs)
|
|
central_system_v21.chargepoint.route_map = create_route_map(
|
|
central_system_v21.chargepoint
|
|
)
|
|
|
|
# Q01.FR.03: Check all the fields of NotifyEVChargingNeeds
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"NotifyEVChargingNeeds",
|
|
{"evseId": 1, "requestedEnergyTransfer": "DC",
|
|
"controlMode": "DynamicControl", "mobilityNeedsMode": "EVCC"},
|
|
validate_notify_ev_charging_needs
|
|
)
|
|
# Check variables after NotifyEVChargingNeeds, so that we don't have to guess when the variables have been updated
|
|
ev_available = GetVariableDataType(component=ComponentType(name="ConnectedEV", evse=EVSEType(id=1)),
|
|
variable=VariableType(name="Available"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
ev_vehicle_id = GetVariableDataType(component=ComponentType(name="ConnectedEV", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="VehicleId"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
ev_protocol_agreed = GetVariableDataType(component=ComponentType(name="ConnectedEV", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="ProtocolAgreed"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
ev_protocol_supported_by_ev = GetVariableDataType(component=ComponentType(name="ConnectedEV", evse=EVSEType(id=1)),
|
|
variable=VariableType(
|
|
name="ProtocolSupportedByEV", instance="1"),
|
|
attribute_type=AttributeEnumType.actual)
|
|
list_of_vars = [ev_available, ev_vehicle_id,
|
|
ev_protocol_agreed, ev_protocol_supported_by_ev]
|
|
r: call_result21.GetVariables = await charge_point_v21.get_variables_req(get_variable_data=list_of_vars)
|
|
# TODO(mlitre): Add check on VehicleCertificate when it is supported
|
|
|
|
# Q01.FR.36: Validate ConnectedEV variables
|
|
for result in r.get_variable_result:
|
|
assert result['attribute_status'] == GetVariableStatusEnumType.accepted
|
|
if result['variable']['name'] == 'Available':
|
|
assert result['attribute_value'] == 'true'
|
|
elif result['variable']['name'] == 'VehicleId':
|
|
# TODO(mlitre): Do we know the value before hand to check?
|
|
assert result['attribute_value']
|
|
elif result['variable']['name'] == 'ProtocolAgreed':
|
|
assert result['attribute_value'] == 'urn:iso:std:iso:15118:-20:DC,1,0'
|
|
elif result['variable']['name'] == 'ProtocolSupportedByEV':
|
|
# TODO(mlitre): How many should we check?
|
|
assert result['attribute_value']
|
|
|
|
# Q01.FR.06
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"TransactionEvent",
|
|
{"eventType": "Ended", "transactionInfo": {
|
|
"stoppedReason": "ReqEnergyTransferRejected"}, "triggerReason": "AbnormalCondition"},
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.xdist_group(name="ISO15118")
|
|
@pytest.mark.ocpp_version("ocpp2.1")
|
|
@pytest.mark.everest_core_config("everest-config-ocpp201-sil-dc-d20-eim.yaml")
|
|
@pytest.mark.ocpp_config_adaptions(
|
|
GenericOCPP2XConfigAdjustment(
|
|
[
|
|
(
|
|
OCPP2XConfigVariableIdentifier(
|
|
"InternalCtrlr", "SupportedOcppVersions", "Actual"
|
|
),
|
|
"ocpp2.1",
|
|
)
|
|
]
|
|
)
|
|
)
|
|
async def test_q02_no_service_renegotiation(
|
|
central_system_v21: CentralSystem,
|
|
test_controller: TestController,
|
|
test_utility: TestUtility,
|
|
):
|
|
"""
|
|
Q02
|
|
...
|
|
"""
|
|
|
|
log.info(
|
|
"##################### Q02: Starting in operationMode ChargingOnly before enabling V2X #################"
|
|
)
|
|
id_token = IdTokenType(id_token="8BADF00D",
|
|
type="ISO14443")
|
|
|
|
test_controller.start()
|
|
charge_point_v21 = await central_system_v21.wait_for_chargepoint(wait_for_bootnotification=True)
|
|
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"StatusNotification",
|
|
call21.StatusNotification(
|
|
1, ConnectorStatusEnumType.available, 1, datetime.now().isoformat()
|
|
),
|
|
validate_status_notification_201,
|
|
)
|
|
test_controller.plug_in_dc_iso()
|
|
|
|
# Make sure we don't start BPT
|
|
@on(Action.authorize)
|
|
def on_authorize(**kwargs):
|
|
return call_result21.Authorize(
|
|
id_token_info=IdTokenInfoType(
|
|
status=AuthorizationStatusEnumType.accepted,
|
|
group_id_token=IdTokenType(
|
|
id_token="12345", type="Central"
|
|
),
|
|
),
|
|
allowed_energy_transfer=[EnergyTransferModeEnumType.dc]
|
|
)
|
|
|
|
setattr(charge_point_v21, "on_authorize", on_authorize)
|
|
central_system_v21.chargepoint.route_map = create_route_map(
|
|
central_system_v21.chargepoint
|
|
)
|
|
test_controller.swipe(id_token.id_token)
|
|
r: call21.TransactionEvent = call21.TransactionEvent(
|
|
**await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"TransactionEvent",
|
|
{"eventType": "Started"},
|
|
)
|
|
)
|
|
|
|
transaction = TransactionType(**r.transaction_info)
|
|
|
|
assert await wait_for_and_validate(
|
|
test_utility,
|
|
charge_point_v21,
|
|
"NotifyEVChargingNeeds",
|
|
{"evseId": 1, "requestedEnergyTransfer": "DC",
|
|
"controlMode": "DynamicControl", "mobilityNeedsMode": "EVCC"},
|
|
validate_notify_ev_charging_needs
|
|
)
|
|
r: call_result21.NotifyAllowedEnergyTransfer = await charge_point_v21.notify_allowed_energy_transfer_request(allowed_energy_transfer=[EnergyTransferModeEnumType.dc_bpt], transaction_id=transaction.transaction_id)
|
|
# TODO(mlitre): Once service renegotiation is supported expect Accepted instead of rejected
|
|
assert r.status == NotifyAllowedEnergyTransferStatusEnumType.rejected
|