Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/test data and fixes #276

Merged
11 changes: 9 additions & 2 deletions iso15118/secc/controller/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
PVEVEnergyRequest,
PVEVMaxCurrent,
PVEVMaxCurrentLimit,
PVEVMaxPowerLimit,
PVEVMaxVoltage,
PVEVMaxVoltageLimit,
PVEVSEMaxCurrentLimit,
Expand Down Expand Up @@ -92,11 +93,14 @@ class AuthorizationResponse:

@dataclass
class EVDataContext:
dc_current: Optional[float] = None
dc_voltage: Optional[float] = None
dc_current_request: Optional[int] = None
dc_voltage_request: Optional[int] = None
ac_current: Optional[dict] = None # {"l1": 10, "l2": 10, "l3": 10}
ac_voltage: Optional[dict] = None # {"l1": 230, "l2": 230, "l3": 230}
soc: Optional[int] = None # 0-100
remaining_time_to_full_soc_s: Optional[int] = None
remaining_time_to_bulk_soc_s: Optional[int] = None
evcc_id: Optional[str] = None

# from ISO 15118-20 AC
departure_time: Optional[int] = None
Expand Down Expand Up @@ -146,6 +150,7 @@ class ServiceStatus(str, Enum):
class EVChargeParamsLimits:
ev_max_voltage: Optional[Union[PVEVMaxVoltageLimit, PVEVMaxVoltage]] = None
ev_max_current: Optional[Union[PVEVMaxCurrentLimit, PVEVMaxCurrent]] = None
ev_max_power: Optional[PVEVMaxPowerLimit] = None
e_amount: Optional[PVEAmount] = None
ev_energy_request: Optional[PVEVEnergyRequest] = None

Expand All @@ -169,10 +174,12 @@ class EVSessionContext15118:
class EVSEControllerInterface(ABC):
def __init__(self):
self.ev_data_context = EVDataContext()
self.ev_charge_params_limits = EVChargeParamsLimits()
self._selected_protocol = Optional[Protocol]

def reset_ev_data_context(self):
self.ev_data_context = EVDataContext()
self.ev_charge_params_limits = EVChargeParamsLimits()

def get_ev_data_context(self) -> EVDataContext:
return self.ev_data_context
Expand Down
44 changes: 44 additions & 0 deletions iso15118/secc/states/din_spec_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Optional, Type, Union

from iso15118.secc.comm_session_handler import SECCCommunicationSession
from iso15118.secc.controller.interface import EVChargeParamsLimits
from iso15118.secc.states.secc_state import StateSECC
from iso15118.shared.messages.app_protocol import (
SupportedAppProtocolReq,
Expand Down Expand Up @@ -127,6 +128,9 @@ async def process_message(
)

self.comm_session.evcc_id = session_setup_req.evcc_id
self.comm_session.evse_controller.ev_data_context.evcc_id = (
session_setup_req.evcc_id
)
self.comm_session.session_id = session_id

self.create_next_message(
Expand Down Expand Up @@ -381,6 +385,29 @@ async def process_message(
charge_parameter_discovery_req.requested_energy_mode
)

ev_max_voltage = (
charge_parameter_discovery_req.dc_ev_charge_parameter.ev_maximum_voltage_limit # noqa: E501
)
ev_max_current = (
charge_parameter_discovery_req.dc_ev_charge_parameter.ev_maximum_current_limit # noqa: E501
)
ev_energy_request = (
charge_parameter_discovery_req.dc_ev_charge_parameter.ev_energy_request
)
ev_max_power = (
charge_parameter_discovery_req.dc_ev_charge_parameter.ev_maximum_power_limit
)
ev_charge_params_limits = EVChargeParamsLimits(
ev_max_voltage=ev_max_voltage,
ev_max_current=ev_max_current,
ev_max_power=ev_max_power,
ev_energy_request=ev_energy_request,
)

self.comm_session.evse_controller.ev_charge_params_limits = (
ev_charge_params_limits
)

dc_evse_charge_params = (
await self.comm_session.evse_controller.get_dc_evse_charge_parameter() # noqa
)
Expand Down Expand Up @@ -762,6 +789,23 @@ async def process_message(
self.comm_session.evse_controller.ev_data_context.soc = (
current_demand_req.dc_ev_status.ev_ress_soc
)

self.comm_session.evse_controller.ev_data_context.remaining_time_to_bulk_soc_s = ( # noqa: E501
None
if current_demand_req.remaining_time_to_bulk_soc is None
else current_demand_req.remaining_time_to_bulk_soc.get_decimal_value()
)

self.comm_session.evse_controller.ev_data_context.remaining_time_to_full_soc_s = ( # noqa: E501
None
if current_demand_req.remaining_time_to_full_soc is None
else current_demand_req.remaining_time_to_full_soc.get_decimal_value()
)

self.comm_session.evse_controller.ev_charge_params_limits.ev_max_current = (
current_demand_req.ev_max_current_limit
)

await self.comm_session.evse_controller.send_charging_command(
current_demand_req.ev_target_voltage, current_demand_req.ev_target_current
)
Expand Down
31 changes: 28 additions & 3 deletions iso15118/secc/states/iso15118_2_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ async def process_message(
)

self.comm_session.evcc_id = session_setup_req.evcc_id
self.comm_session.evse_controller.ev_data_context.evcc_id = (
session_setup_req.evcc_id
)
self.comm_session.session_id = session_id

self.create_next_message(
Expand Down Expand Up @@ -1299,13 +1302,21 @@ async def process_message(
ev_energy_request = (
charge_params_req.dc_ev_charge_parameter.ev_energy_request
)
ev_max_power = (
charge_params_req.dc_ev_charge_parameter.ev_maximum_power_limit
)
ev_charge_params_limits = EVChargeParamsLimits(
ev_max_voltage=ev_max_voltage,
ev_max_current=ev_max_current,
ev_max_power=ev_max_power,
ev_energy_request=ev_energy_request,
)
departure_time = charge_params_req.dc_ev_charge_parameter.departure_time

self.comm_session.evse_controller.ev_charge_params_limits = (
ev_charge_params_limits
)

if not departure_time:
departure_time = 0
sa_schedule_list = await self.comm_session.evse_controller.get_sa_schedule_list(
Expand Down Expand Up @@ -2334,6 +2345,23 @@ async def process_message(
self.comm_session.evse_controller.ev_data_context.soc = (
current_demand_req.dc_ev_status.ev_ress_soc
)

self.comm_session.evse_controller.ev_data_context.remaining_time_to_bulk_soc_s = ( # noqa: E501
None
if current_demand_req.remaining_time_to_bulk_soc is None
else current_demand_req.remaining_time_to_bulk_soc.get_decimal_value()
)

self.comm_session.evse_controller.ev_data_context.remaining_time_to_full_soc_s = ( # noqa: E501
None
if current_demand_req.remaining_time_to_full_soc is None
else current_demand_req.remaining_time_to_full_soc.get_decimal_value()
)

self.comm_session.evse_controller.ev_charge_params_limits.ev_max_current = (
current_demand_req.ev_max_current_limit
)

await self.comm_session.evse_controller.send_charging_command(
current_demand_req.ev_target_voltage, current_demand_req.ev_target_current
)
Expand Down Expand Up @@ -2436,9 +2464,6 @@ async def process_message(
return

welding_detection_res = WeldingDetectionRes(
# todo llr: java exi codec throws error with this message.
# Exception Description: No conversion value provided for the value [OK]
# in field [ns5:WeldingDetectionRes.ns5:ResponseCode/text()].
response_code=ResponseCode.OK,
dc_evse_status=await self.comm_session.evse_controller.get_dc_evse_status(),
evse_present_voltage=(
Expand Down
8 changes: 8 additions & 0 deletions iso15118/shared/comm_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,17 @@ async def process_message(self, message: bytes):
exc.message,
self.get_exi_ns(v2gtp_msg.payload_type),
)
logger.error(
f"EXI message (ns={self.get_exi_ns(v2gtp_msg.payload_type)}) "
f"where validation failed: {v2gtp_msg.payload.hex()}"
)
return
except EXIDecodingError as exc:
logger.exception(f"{exc}")
logger.error(
f"EXI message (ns={self.get_exi_ns(v2gtp_msg.payload_type)}) "
f"where error occured: {v2gtp_msg.payload.hex()}"
)
raise exc

# Shouldn't happen, but just to be sure (otherwise mypy would complain)
Expand Down
4 changes: 3 additions & 1 deletion iso15118/shared/exi_codec.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ def to_exi(self, msg_element: BaseModel, protocol_ns: str) -> bytes:
try:
exi_stream = self.exi_codec.encode(msg_content, protocol_ns)
except Exception as exc:
logger.error(f"EXIEncodingError for {str(msg_element)}: {exc}")
logger.error(
f"EXIEncodingError in {protocol_ns} with {str(msg_content)}: {exc}"
)
raise EXIEncodingError(
f"EXIEncodingError for {str(msg_element)}: " f"{exc}"
) from exc
Expand Down
7 changes: 6 additions & 1 deletion iso15118/shared/messages/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class PhysicalValue(BaseModel):
"""

_max_limit: int = 0
_min_limit: int = 0
# XSD int16 range [-32768, 32767]
value: int = Field(..., ge=INT_16_MIN, le=INT_16_MAX, alias="Value")
# XSD type byte with value range [-3..3]
Expand All @@ -44,7 +45,7 @@ def validate_value_range(cls, values):
value = values.get("value")
multiplier = values.get("multiplier")
calculated_value = value * 10**multiplier
if calculated_value > cls._max_limit or calculated_value < 0:
if calculated_value > cls._max_limit or calculated_value < cls._min_limit:
raise ValueError(
f"{cls.__name__[2:]} value limit exceeded: {calculated_value} \n"
f"Max: {cls._max_limit} \n"
Expand Down Expand Up @@ -483,8 +484,12 @@ class PVEVTargetCurrentDin(PVEVTargetCurrent):
See section 9.5.2.4 in DIN SPEC 70121

In DIN the Element unit is optional, in ISO it is mandatory.
In DIN there is no range for the value specified.
There are EVs that sometimes send values below zero
(e.g. Skoda Enyaq).
"""

_min_limit: int = -10
unit: Literal[UnitSymbol.AMPERE] = Field(None, alias="Unit")
Comment on lines +488 to 493
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity - when does this usually happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can happen when charging starts. I don't know why but some times they request a negative current as first current demand request.



Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ def comm_secc_session_mock():
comm_session_mock.protocol = Protocol.UNKNOWN
comm_session_mock.evse_id = "UK123E1234"
comm_session_mock.ev_session_context = EVSessionContext15118()
comm_session_mock.selected_schedule = 1
return comm_session_mock
Empty file added tests/dinspec/__init__.py
Empty file.
8 changes: 8 additions & 0 deletions tests/shared/messages/exi_message_container.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from dataclasses import dataclass


@dataclass
class ExiMessageContainer:
message_name: str
json_str: str
description: str = ""
Loading