Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import threading

from queue import Queue
from typing import Any, Callable
from typing import Any, Callable, Optional

from everest.framework import Module, RuntimeSession
from everest.framework import error

class ProbeModule:
"""
Expand Down Expand Up @@ -108,6 +109,49 @@
lambda message, _queue=queue: _queue.put(message))
return queue

def raise_error(self, implementation_id: str, error_obj: error.Error):
"""
Raise an error from an interface the probe module implements.
- implementation_id: the id of the implementation, as used by other modules requiring it in the runtime config
- error_obj: the Error object to raise
"""
self._mod.raise_error(implementation_id, error_obj)

def clear_error(self, implementation_id: str, error_type: str, sub_type: Optional[str] = None):
"""
Clear an error from an interface the probe module implements.
- implementation_id: the id of the implementation, as used by other modules requiring it in the runtime config
- error_type: the type of the error to clear
- sub_type: optional sub-type of the error to clear
"""
if sub_type is not None:
self._mod.clear_error(implementation_id, error_type, sub_type)
else:
self._mod.clear_error(implementation_id, error_type)

def subscribe_error(self, connection_id: str, error_type: str,

Check notice on line 132 in applications/utils/everest-testing/src/everest/testing/core_utils/probe_module.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

applications/utils/everest-testing/src/everest/testing/core_utils/probe_module.py#L132

Trailing whitespace
callback: Callable[[error.Error], None],

Check notice on line 133 in applications/utils/everest-testing/src/everest/testing/core_utils/probe_module.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

applications/utils/everest-testing/src/everest/testing/core_utils/probe_module.py#L133

Trailing whitespace
clear_callback: Callable[[error.Error], None]):
"""
Subscribe to a specific error type from a module required by the probe module.
- connection_id: the id of the connection, as specified for the probe module in the runtime config
- error_type: the type of errors to subscribe to
- callback: a function to handle when the error is raised, accepting an Error object
- clear_callback: a function to handle when the error is cleared, accepting an Error object
"""
self._mod.subscribe_error(self._setup.connections[connection_id][0], error_type, callback, clear_callback)

def subscribe_all_errors(self, connection_id: str,
callback: Callable[[error.Error], None],
clear_callback: Callable[[error.Error], None]):
"""
Subscribe to all errors from a module required by the probe module.
- connection_id: the id of the connection, as specified for the probe module in the runtime config
- callback: a function to handle when any error is raised, accepting an Error object
- clear_callback: a function to handle when any error is cleared, accepting an Error object
"""
self._mod.subscribe_all_errors(self._setup.connections[connection_id][0], callback, clear_callback)

def _ready(self):
"""
Internal function: callback triggered by the EVerest framework when all modules have been initialized
Expand Down
6 changes: 6 additions & 0 deletions modules/EVSE/EvseManager/Charger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ void Charger::run_state_machine() {

case EvseState::WaitingForAuthentication:

// Wait here until all errors are cleared
if (stop_charging_on_fatal_error_internal()) {
signal_hlc_error(types::iso15118::EvseError::Error_EmergencyShutdown);
break;
}

// Explicitly do not allow to be powered on. This is important
// to make sure control_pilot does not switch on relais even if
// we start PWM here
Expand Down
30 changes: 0 additions & 30 deletions tests/core_tests/basic_charging_tests.py

This file was deleted.

169 changes: 169 additions & 0 deletions tests/core_tests/smoke_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
# Copyright Pionix GmbH and Contributors to EVerest

import pytest
import asyncio
from unittest.mock import Mock

from everest.testing.core_utils.common import Requirement
from everest.testing.core_utils.fixtures import *

Check warning on line 10 in tests/core_tests/smoke_tests.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/core_tests/smoke_tests.py#L10

'everest.testing.core_utils.fixtures.*' imported but unused (F401)
from everest.testing.core_utils.controller.test_controller_interface import TestController
from everest.testing.core_utils.everest_core import EverestCore
from everest.testing.core_utils.probe_module import ProbeModule

async def wait_for_session_events(mock, expected_events, timeout=30):
"""Wait for specific events to appear in the mock's call list."""
start_time = asyncio.get_event_loop().time()

Check notice on line 18 in tests/core_tests/smoke_tests.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/core_tests/smoke_tests.py#L18

Trailing whitespace
while asyncio.get_event_loop().time() - start_time < timeout:
events = []
for call in mock.call_args_list:
event_data = call[0][0]
event_type = event_data.get('event')
events.append(event_type)

if all(event in events for event in expected_events):
return events

await asyncio.sleep(0.1)

raise TimeoutError(f"Timeout waiting for events {expected_events}. Got: {events}")


async def wait_for_ready(mock, timeout=5):
"""Wait until the ready mock has been called."""
start_time = asyncio.get_event_loop().time()

while asyncio.get_event_loop().time() - start_time < timeout:
if mock.call_count > 0:
return
await asyncio.sleep(0.1)

raise TimeoutError("Timeout waiting for ready signal.")


async def wait_for_error(mock, timeout=5):
"""Wait until the error mock has been called."""
start_time = asyncio.get_event_loop().time()

while asyncio.get_event_loop().time() - start_time < timeout:
if mock.call_count > 0:
return
await asyncio.sleep(0.1)

raise TimeoutError("Timeout waiting for error signal.")

async def setup_probe_module(test_controller: TestController, everest_core: EverestCore):
"""Initialize test controller and probe module, wait for ready. Returns probe_module."""
test_controller.start()
probe_module = ProbeModule(everest_core.get_runtime_session())

ready_mock = Mock()
probe_module.subscribe_variable('evse_manager', 'ready', ready_mock)

probe_module.start()
await probe_module.wait_to_be_ready()
await wait_for_ready(ready_mock, timeout=5)

return probe_module


def setup_session_event_monitoring(probe_module: ProbeModule, connection_id: str):
"""Subscribe to session events from a connection. Returns session_event_mock."""
session_event_mock = Mock()
probe_module.subscribe_variable(connection_id, 'session_event', session_event_mock)
return session_event_mock


def setup_error_monitoring(probe_module: ProbeModule, connection_id: str):
"""Subscribe to error events from a connection. Returns error_raised_mock and error_cleared_mock."""
error_raised_mock = Mock()
error_cleared_mock = Mock()
probe_module.subscribe_all_errors(connection_id, error_raised_mock, error_cleared_mock)
return error_raised_mock, error_cleared_mock


@pytest.mark.asyncio
@pytest.mark.probe_module(connections={"evse_manager": [Requirement("connector_1", "evse")]})
@pytest.mark.everest_core_config('config-sil.yaml')
async def test_pwm_ac_session(test_controller: TestController, everest_core: EverestCore):
"""
Test session events of a basic PWM AC charging session.
"""
probe_module = await setup_probe_module(test_controller, everest_core)
session_event_mock = setup_session_event_monitoring(probe_module, 'evse_manager')

expected_events = ['SessionStarted', 'AuthRequired', 'Authorized',

Check notice on line 97 in tests/core_tests/smoke_tests.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/core_tests/smoke_tests.py#L97

Trailing whitespace
'TransactionStarted', 'PrepareCharging', 'ChargingStarted']

test_controller.plug_in()
await wait_for_session_events(session_event_mock, expected_events)

test_controller.plug_out()
await wait_for_session_events(session_event_mock, ['TransactionFinished', 'SessionFinished'])


@pytest.mark.asyncio
@pytest.mark.probe_module(connections={"evse_manager": [Requirement("connector_1", "evse")]})
@pytest.mark.everest_core_config('config-sil.yaml')
async def test_iso15118_ac_session(test_controller: TestController, everest_core: EverestCore):
"""
Test session events of an ISO 15118 AC charging session.
"""
probe_module = await setup_probe_module(test_controller, everest_core)
session_event_mock = setup_session_event_monitoring(probe_module, 'evse_manager')

expected_events = ['SessionStarted', 'AuthRequired', 'Authorized',
'TransactionStarted', 'PrepareCharging', 'ChargingStarted']

test_controller.plug_in_ac_iso()
await wait_for_session_events(session_event_mock, expected_events)

test_controller.plug_out()
await wait_for_session_events(session_event_mock, ['TransactionFinished', 'SessionFinished'])

@pytest.mark.asyncio
@pytest.mark.probe_module(connections={"evse_manager": [Requirement("evse_manager", "evse")]})
@pytest.mark.everest_core_config('config-sil-dc.yaml')
async def test_iso15118_dc_session(test_controller: TestController, everest_core: EverestCore):
"""
Test session events of an ISO 15118 DC charging session.
"""

probe_module = await setup_probe_module(test_controller, everest_core)
session_event_mock = setup_session_event_monitoring(probe_module, 'evse_manager')

expected_events = ['SessionStarted', 'AuthRequired', 'Authorized',
'TransactionStarted', 'PrepareCharging', 'ChargingStarted']

test_controller.plug_in_dc_iso()
await wait_for_session_events(session_event_mock, expected_events)

test_controller.plug_out()
await wait_for_session_events(session_event_mock, ['TransactionFinished', 'SessionFinished'])

@pytest.mark.asyncio
@pytest.mark.probe_module(connections={"evse_manager": [Requirement("evse_manager", "evse")]})
@pytest.mark.everest_core_config('config-sil-dc.yaml')
async def test_iso15118_dc_session_error_before_session(test_controller: TestController, everest_core: EverestCore):
"""
Test session events of an ISO 15118 DC charging session with an error before the session.
"""

probe_module = await setup_probe_module(test_controller, everest_core)
session_event_mock = setup_session_event_monitoring(probe_module, 'evse_manager')
error_raised_mock, error_cleared_mock = setup_error_monitoring(probe_module, 'evse_manager')

Check notice on line 156 in tests/core_tests/smoke_tests.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/core_tests/smoke_tests.py#L156

Unused variable 'error_cleared_mock'

test_controller.raise_error("MREC2GroundFailure")

# Verify that error was raised
await wait_for_error(error_raised_mock)
assert error_raised_mock.called, "Error should have been raised"

test_controller.plug_in_dc_iso()

# wait for any session events for a short time
await asyncio.sleep(10)

assert session_event_mock.call_count == 0, "No session events should occur while error is active"
Loading