Skip to content

Commit

Permalink
enhanced the ev profile profile validation by skipping entries that w…
Browse files Browse the repository at this point in the history
…ere already scanned and tested
  • Loading branch information
tropxy committed Oct 29, 2022
1 parent 43420a8 commit fbd1f2c
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 15 deletions.
56 changes: 49 additions & 7 deletions iso15118/secc/states/iso15118_2_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -1619,23 +1619,45 @@ def _is_charging_profile_valid(self, power_delivery_req: PowerDeliveryReq) -> bo
# PMaxScheduleEntry_1, ...]. The enumerate will pair an index with
# each list entry: (0, PMaxScheduleEntry_0), (1,
# PMaxScheduleEntry_1), ...
cached_start_idx: int = 0
last_ev_running_idx: int = 0
for idx, sa_profile_entry in enumerate(schedule_entries):
sa_profile_entry_start = sa_profile_entry.time_interval.start
sa_entry_pmax = (
sa_profile_entry.p_max.value
* 10**sa_profile_entry.p_max.multiplier
)
for (
ev_profile_entry
) in power_delivery_req.charging_profile.profile_entries:
ev_profile = power_delivery_req.charging_profile
ev_profile_entries = ev_profile.profile_entries

# The cached index and last ev running index are helpers that
# allow the for loop to start in the ev profile entry belonging
# to the start range of the SA entry. This avoids looping all
# over the ev profiles again for each SA entry schedule.
# For example, if the SA time schedule is
# [0; 1000[ [1000; 2000[ [2000; 3000[
# And the EV profile time schedule is
# [0, 100[ [100, 400[ [400; 1000[ [1000, inf+[
# The first three entries of the ev profile belong
# to the time range [0; 1000[ of the SA schedule and
# if the EV power, for those three entries
# does not surpass the limit imposed by the SA schedule
# during that time, then for the next loop iteration,
# we dont need to go test those EV time entries again,
# as they dont belong to the [1000; 2000[ SA time schedule slot.
cached_start_idx += last_ev_running_idx
last_ev_running_idx = 0

for (ev_profile_idx, ev_profile_entry) in enumerate(
ev_profile_entries[cached_start_idx:]
):
try:
# By getting the next entry/slot, we can know when
# the current entry ends
next_sa_profile_entry = schedule_entries[idx + 1]
sa_profile_entry_end = (
next_sa_profile_entry.time_interval.start
)

except IndexError:
# As the index is out of rage, it signals this is
# the final entry of the sa profile. The last entry
Expand All @@ -1646,12 +1668,32 @@ def _is_charging_profile_valid(self, power_delivery_req: PowerDeliveryReq) -> bo
sa_profile_entry_start
+ sa_profile_entry.time_interval.duration
)

_is_last_ev_profile = (
ev_profile_entry.start == ev_profile_entries[-1].start
)
# fmt: off
if sa_profile_entry_start <= ev_profile_entry.start <= sa_profile_entry_end: # noqa
ev_entry_pmax = (ev_profile_entry.max_power.value
* 10 ** ev_profile_entry.max_power.multiplier) # noqa
if sa_profile_entry_start <= ev_profile_entry.start < sa_profile_entry_end or _is_last_ev_profile: # noqa
ev_entry_pmax = ev_profile_entry.max_power.value * 10 ** ev_profile_entry.max_power.multiplier # noqa
if ev_entry_pmax > sa_entry_pmax:
logger.error(
f"EV Profile start {ev_profile_entry.start}s"
f"is out of power range: "
f"EV Max {ev_entry_pmax} W > EVSE Max "
f"{sa_entry_pmax} W \n")
return False
if not _is_last_ev_profile:
last_ev_running_idx = ev_profile_idx + 1
else:
logger.debug(
f"EV last Profile start "
f"{ev_profile_entry.start}s is "
f"within time range [{sa_profile_entry_start}; "
f"{sa_profile_entry_end}[ and power range: "
f"EV Max {ev_entry_pmax} W <= EVSE Max "
f"{sa_entry_pmax} W \n")
else:
break
# fmt: on
return True

Expand Down
11 changes: 3 additions & 8 deletions tests/secc/states/test_iso15118_2_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
get_charge_parameter_discovery_req_message_departure_time_one_hour,
get_charge_parameter_discovery_req_message_no_departure_time,
get_dummy_charging_status_req,
get_dummy_sa_schedule,
get_dummy_v2g_message_authorization_req,
get_dummy_v2g_message_payment_details_req,
get_dummy_v2g_message_power_delivery_req_charge_start,
Expand Down Expand Up @@ -393,16 +394,10 @@ async def test_charge_parameter_discovery_req_v2g2_225_out_of_boundary(self):
# message sent by the SECC.
self.comm_session.writer = Mock()
self.comm_session.writer.get_extra_info = Mock()
charge_parameter_discovery = ChargeParameterDiscovery(self.comm_session)
await charge_parameter_discovery.process_message(
message=get_charge_parameter_discovery_req_message_departure_time_one_hour()
)

assert (
charge_parameter_discovery.message.body.charge_parameter_discovery_res
is not None
)
self.comm_session.offered_schedules = get_dummy_sa_schedule()
power_delivery = PowerDelivery(self.comm_session)

await power_delivery.process_message(
message=get_v2g_message_power_delivery_req_charging_profile_out_of_boundary() # noqa
)
Expand Down
100 changes: 100 additions & 0 deletions tests/secc/states/test_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,71 @@ def get_v2g_message_power_delivery_req_charging_profile_in_boundary():
)


def get_dummy_sa_schedule():
sa_schedule_list: list[SAScheduleTuple] = []
# PMaxSchedule
p_max_schedule_entry_1 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=11000, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(start=0),
)
p_max_schedule_entry_2 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=10000, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(start=1000),
)
p_max_schedule_entry_3 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=8000, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(start=1500),
)
p_max_schedule_entry_4 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=10500, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(start=2000),
)
p_max_schedule_entry_5 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=9530, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(start=2100),
)
p_max_schedule_entry_6 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=10530, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(start=2500),
)
p_max_schedule_entry_7 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=8535, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(start=3000),
)
p_max_schedule_entry_8 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=8215, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(start=3100),
)

p_max_schedule_entry_9 = PMaxScheduleEntry(
p_max=PVPMax(multiplier=0, value=7000, unit=UnitSymbol.WATT),
time_interval=RelativeTimeInterval(
start=3500,
duration=100,
),
)
p_max_schedule = PMaxSchedule(
schedule_entries=[
p_max_schedule_entry_1,
p_max_schedule_entry_2,
p_max_schedule_entry_3,
p_max_schedule_entry_4,
p_max_schedule_entry_5,
p_max_schedule_entry_6,
p_max_schedule_entry_7,
p_max_schedule_entry_8,
p_max_schedule_entry_9,
]
)
# Putting the list of SAScheduleTuple entries together
sa_schedule_tuple = SAScheduleTuple(
sa_schedule_tuple_id=1,
p_max_schedule=p_max_schedule,
)
sa_schedule_list.append(sa_schedule_tuple)
return sa_schedule_list


def get_v2g_message_power_delivery_req_charging_profile_out_of_boundary():
charging_profile = ChargingProfile(
profile_entries=[
Expand All @@ -284,6 +349,41 @@ def get_v2g_message_power_delivery_req_charging_profile_out_of_boundary():
),
ProfileEntryDetails(
start=2000,
max_power=PVPMax(multiplier=0, value=5000, unit=UnitSymbol.WATT),
max_phases_in_use=3,
),
ProfileEntryDetails(
start=2100,
max_power=PVPMax(multiplier=0, value=6300, unit=UnitSymbol.WATT),
max_phases_in_use=3,
),
ProfileEntryDetails(
start=2300,
max_power=PVPMax(multiplier=0, value=2000, unit=UnitSymbol.WATT),
max_phases_in_use=3,
),
ProfileEntryDetails(
start=2400,
max_power=PVPMax(multiplier=0, value=4000, unit=UnitSymbol.WATT),
max_phases_in_use=3,
),
ProfileEntryDetails(
start=2450,
max_power=PVPMax(multiplier=0, value=700, unit=UnitSymbol.WATT),
max_phases_in_use=3,
),
ProfileEntryDetails(
start=2500,
max_power=PVPMax(multiplier=0, value=6999, unit=UnitSymbol.WATT),
max_phases_in_use=3,
),
ProfileEntryDetails(
start=3000,
max_power=PVPMax(multiplier=0, value=1400, unit=UnitSymbol.WATT),
max_phases_in_use=3,
),
ProfileEntryDetails(
start=3100,
max_power=PVPMax(multiplier=0, value=8000, unit=UnitSymbol.WATT),
max_phases_in_use=3,
),
Expand Down

0 comments on commit fbd1f2c

Please sign in to comment.