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

Fix compatibility with linux #2

Merged
merged 16 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
9 changes: 2 additions & 7 deletions .env.dev → .env.dev.docker
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
# General Settings
NETWORK_INTERFACE=eth0

MQTT_HOST=mqtt
MQTT_PORT=9001

REDIS_PORT=6379
REDIS_HOST=redis

# LOG Settings
LOG_LEVEL="DEBUG"

# SECC Settings
15118_MQTT_SUBSCRIBE_TOPIC=iso15118/cs
15118_MQTT_PUBLISH_TOPIC=iso15118/josev
FREE_CHARGING_SERVICE=False
FREE_CERT_INSTALL_SERVICE=True
ALLOW_CERT_INSTALL_SERVICE=True
Expand All @@ -22,3 +15,5 @@ SECC_ENFORCE_TLS=False
USE_TLS=False
EVCC_ENFORCE_TLS=False

# LOG Settings
LOG_LEVEL="DEBUG"
10 changes: 3 additions & 7 deletions .env.local-run → .env.dev.local
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
# General Settings
NETWORK_INTERFACE=eth0

MQTT_HOST=localhost
MQTT_PORT=9001

REDIS_PORT=10001
REDIS_HOST=localhost

# LOG Settings
LOG_LEVEL="DEBUG"

# SECC Settings
15118_MQTT_SUBSCRIBE_TOPIC=iso15118/cs
15118_MQTT_PUBLISH_TOPIC=iso15118/josev
FREE_CHARGING_SERVICE=False
FREE_CERT_INSTALL_SERVICE=True
ALLOW_CERT_INSTALL_SERVICE=True
Expand All @@ -22,3 +15,6 @@ SECC_ENFORCE_TLS=False
USE_TLS=False
EVCC_ENFORCE_TLS=False

# LOG Settings
LOG_LEVEL="DEBUG"

2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# For more info about the structure and rules, check:
# https://docs.gitlab.com/ee/user/project/code_owners.html

@tropxy @mueltin @shalinnijel2
@tropxy @MarcMueltin @shalinnijel2



23 changes: 18 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# The shell to run the makefile with must be defined to work properly in Linux systems
SHELL := /bin/bash

# all the recipes are phony (no files to check).
.PHONY: .install-poetry docs tests build dev run poetry-update poetry-install install-local run-evcc run-secc run-ocpp mypy reformat black flake8 code-quality

Expand Down Expand Up @@ -36,15 +39,25 @@ tests: .install-poetry
#poetry run flake8 pytest -vv tests
poetry run pytest -vv tests

build:
.generate_v2_certs:
cd iso15118/shared/pki; ./create_certs.sh -v iso-2

.generate_v20_certs:
cd iso15118/shared/pki; ./create_certs.sh -v iso-20

build: .check-env-vars .generate_v2_certs
# `xargs` will copy the Dockerfile template, so that it can be individually
# used by the secc and evcc services
xargs -n 1 cp -v template.Dockerfile<<<"iso15118/evcc/Dockerfile iso15118/secc/Dockerfile"
# @ is used as a separator and allow us to escape '/', so we can substitute the '/' itself
# This command will convert: 's/secc/secc/g' -> 's/secc/evcc/g'
sed -i '.bkp' 's@/secc/g@/evcc/g@g' iso15118/evcc/Dockerfile
# The following command will convert: 's/secc/secc/g' -> 's/secc/evcc/g',
# in the evcc Dockerfile.
# This conversion is required, otherwise we wouldn't be able to spawn the evcc start script.
# @ is used as a separator and allows us to escape '/', so we can substitute the '/' itself
sed -i.bkp 's@/secc/g@/evcc/g@g' iso15118/evcc/Dockerfile
docker-compose build

dev:
# the dev file apply changes to the original compose file
# the dev file apply changes to the original compose file
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up

run:
Expand Down
129 changes: 91 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ The primary dependencies to install the project are the following:

Also, since the project depends on external custom packages, it is necessary
to set the credentials for the Switch PYPI server as ENVs:
```shell
$ export PYPI_USER=****
$ export PYPI_PASS=****
```

Contact André <[email protected]> if you require the credentials.
```shell
$ export PYPI_USER=****
$ export PYPI_PASS=****
```

Contact André <[email protected]>, if you require the credentials.

There are two recommended ways of running the project:

Expand All @@ -28,73 +29,123 @@ There are two recommended ways of running the project:
$ make build
$ make dev
```
Currently, only SECC will be spawned as the goal of JOSEV is to run iso15118
as an SECC


2. Local Installation

Install JRE engine with the following command:

```bash
apt update && apt install -y default-jre

```
The JRE engine is only a temporary requirement until we replace the Java-based EXI codec (EXIficient)[^4] with our own RUST-based EXI codec.


The JRE engine is only a temporary requirement until we replace the Java-based
EXI codec (EXIficient)[^4] with our own Rust-based EXI codec.

Install the module using `poetry` and run the main script related
to the EVCC or SECC instance you want to run. Switch to the iso15118 directory
and run:

```bash
$ poetry update
$ poetry install
$ python iso15118/secc/start_secc.py # or python iso15118/evcc/start_evcc.py
```

For convenience, the Makefile, present in the project, helps you to run these
steps. Thus, in the terminal run:

```bash
$ make install-local
$ make run-secc
```

This will call the poetry commands above and run the start script of the
secc.

Option number `1` has the advantage of running within Docker, where everything
is fired up automatically, including tests and linting. Currently, the
docker-compose does not set the `network-mode` as 'host', but this may be
required in order to bridge correctly IPv6 frames.
is fired up automatically, including certificates generation, tests and linting.

Also both SECC and EVCC are spawned, automatically.

The project also requires an MQTT broker connection, so be sure to set up
a broker correctly and to add the necessary credentials and URL.

For more information about the MQTT API used by Switch, please contact us.
For option number `2`, the certificates need to be provided. The project includes
a script to help on the generation of -2 and -20 certificates. This script
is located under `iso15118/shared/pki/` directory and is called `create_certs.sh`.
The following command provides a helper for the script usage:

```bash
$ ./create_certs.sh -h
```

---
**IPv6 WARNING**

Finally, the project includes a few configuration variables whose default
For the system to work locally, the network interface to be used needs to have
an IPv6 local-link address assigned.


For Docker, the `docker-compose.yml` was configured to create an `IPv6` network
called `ipv6_net`, which enables the containers to acquire a local-link address,
which is required to establish an ISO 15118 communication. This configuration is
fine if the user wants to test, in isolation, the EVCC and SECC and allow ISO 15118
communication. This configuration works for both Linux and BSD systems.

However, the usage of an internal `ipv6_net` network, in Docker, does not allow the
host to reach link-local addresses. This would pose a problem, as it would require
the application to use the global-link address, which is not supported by ISO 15118.

The solution is to use the `network_mode: host` feature of Docker, which replicates
the host network topology within the Docker world, i.e. the containers and the
host share the same network. This way, Docker can directly access the virtual
network interface created by the HomePlug Green PHY module, making it possible
to use the local-link address.

Currently, `network_mode: host` just works within Linux environments [^5] [^6].
Since the Switch team relies mostly on MacOS and this project is on a development stage,
`network_mode` is not used by default, but it is possible to use it if the contents of the
file `docker-compose-host-mode.yml` are copied to the main compose file, `docker-compose.yml`.
In that case, it is advised to back up the compose file.


---


## Environment Settings

Finally, the project includes a few configuration variables, whose default
values can be modified by setting them as environmental variables.
The following table provides a few of the available variables:

| ENV | Default Value | Description |
| -------------------------- | --------------------- | ---------------------------------------------------------------------------------------- |
| NETWORK_INTERFACE | `eth0` | HomePlug Green PHY Network Interface from which the high-level communication (HLC) will be established |
| MQTT_HOST | `localhost` | MQTT Broker URL |
| MQTT_PORT | `9001` | MQTT Broker PORT |
| MQTT_USER | `None` | Username for Client Authorization |
| MQTT_PASS | `None` | Password for Client Authorization
| 15118_MQTT_SUBSCRIBE_TOPIC | `iso15118/cs` | Mqtt Subscription Topic
| 15118_MQTT_PUBLISH_TOPIC | `iso15118/josev` | Mqtt Publish Topic
| REDIS_HOST | `localhost` | Redis Host URL
| REDIS_PORT | `10001` | Redis Port
|

The project includes an environment file for dev purposes on the root directoy
`.env.development`, which contains all settings that can be set.

In order to run the project in production, an `.env` file must be created with
the desired settings. This means, if development settings are desired, one can
simply copy the content of `.env.development` to `.env`.
| ENV | Default Value | Description |
| -------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------ |
| NETWORK_INTERFACE | `eth0` | HomePlug Green PHY Network Interface from which the high-level communication (HLC) will be established |
| REDIS_HOST | `localhost` | Redis Host URL |
| REDIS_PORT | `10001` | Redis Port |


The project includes a few environmental files, in the root directory, for
different purposes:

* `.env.dev.docker` - ENV file with development settings, tailored to be used with docker
tropxy marked this conversation as resolved.
Show resolved Hide resolved
* `.env.dev.local` - ENV file with development settings, tailored to be used with
the local host


If the user runs the project locally, e.g. using `$ make build && make run-secc`,
it is required to create a `.env` file, containing the required settings.

This means, if development settings are desired, one can simply copy the contents
of `.env.dev.local` to `.env`.

If Docker is used, the command `make run` will try to get the `.env` file;
The command `make dev` will fetch the contents of `.env.development`.
The command `make dev` will fetch the contents of `.env.dev.docker` - thus,
in this case, the user does not need to create a `.env` file, as Docker will
automatically fetch the `.env.dev.docker` one.

The key-value pair defined in the `.env` file directly affect the settings
tropxy marked this conversation as resolved.
Show resolved Hide resolved
defined in `secc_settings.py` and `evcc_settings.py`. In these scripts, the
user will find all the settings that can be configured.

## Integration Test with an EV Simulator

Expand All @@ -116,3 +167,5 @@ This integration test was tested under:
[^2]: https://www.switch-ev.com/news-and-events/new-features-and-timeline-for-iso15118-20
[^3]: https://python-poetry.org/docs/#installation
[^4]: https://exificient.github.io/
[^5]: https://docs.docker.com/network/host/
[^6]: https://docs.docker.com/desktop/mac/networking/
38 changes: 38 additions & 0 deletions docker-compose-host-mode.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
version: '3.9'
services:
secc:
hostname: secc
build:
context: .
args:
- PYPI_USER=${PYPI_USER}
- PYPI_PASS=${PYPI_PASS}
dockerfile: iso15118/secc/Dockerfile
depends_on:
- mqtt
tropxy marked this conversation as resolved.
Show resolved Hide resolved
- redis
network_mode: host

evcc:
hostname: evcc
build:
context: .
args:
- PYPI_USER=${PYPI_USER}
- PYPI_PASS=${PYPI_PASS}
dockerfile: iso15118/evcc/Dockerfile
depends_on:
- mqtt
- redis
- secc
network_mode: host

mqtt:
build: ./mqtt
ports:
- "9001:9001"

redis:
image: redis:6.2.6-alpine
ports:
- "6379:6379"
4 changes: 2 additions & 2 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version: '3.9'
services:
secc:
env_file: .env.dev
env_file: .env.dev.docker
evcc:
env_file: .env.dev
env_file: .env.dev.docker
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ services:
dockerfile: iso15118/secc/Dockerfile
depends_on:
- redis
networks:
- ipv6_net

evcc:
hostname: evcc
Expand All @@ -22,8 +24,26 @@ services:
depends_on:
- redis
- secc
networks:
- ipv6_net

redis:
image: redis:6.2.6-alpine
ports:
- "6379:6379"


networks:
ipv6_net:
enable_ipv6: true
driver: bridge
# driver_opts is here just a precaution as some docker compose versions do not process the above "enable_ipv6"
driver_opts:
com.docker.network.enable_ipv6: "true"
ipam:
driver: default
config:
#- subnet: 172.16.238.0/24
# gateway: 172.16.238.1
- subnet: 2001:db8:a::/64
gateway: 2001:db8:a::1
11 changes: 1 addition & 10 deletions iso15118/secc/transport/udp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,7 @@ async def __init__.
# aton stands for "Ascii TO Numeric"
multicast_group_bin = socket.inet_pton(socket.AF_INET6, SDP_MULTICAST_GROUP)

nic: str = ""

try:
nic = get_nic(secc_settings.NETWORK_INTERFACE)
except NoLinkLocalAddressError as exc:
Copy link
Contributor

Choose a reason for hiding this comment

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

So there's a guaranteed link-local address? What if someone configures the wrong interface? I believe we still do need this exception, no?

Copy link
Contributor Author

@tropxy tropxy Dec 8, 2021

Choose a reason for hiding this comment

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

No, there is no guarantee. So, first we need to check if the interface specified exists, secondly, we need to check if the interface contains a link-local address. If any of those checks fail, we raise an error and we are not supposed to go on, i.e. the application is supposed to crash.

Before, the code would just check if we had provided an interface and would not even check if the interface actually existed (would also not check, in this case if the interface had a valid link-local address). Later on, in case the interface didn't exist, this would throw an error but wouldnt be completely explicit:

interface_idx = socket.if_nametoindex(nic)

I modified the code to tackle that issue, but I realise now that it is not enough and we also need to check for the validity of the local link

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Due to the changes on handling exceptions I did some reworking on the UDP client and server services, as in some occasions (namely, when an invalid interface was selected) the code would throw the error:

evcc_1   | sys:1: RuntimeWarning: coroutine 'CommunicationSessionHandler.get_from_rcv_queue' was never awaited
evcc_1   | RuntimeWarning: Enable tracemalloc to get the object allocation traceback
evcc_1   | sys:1: RuntimeWarning: coroutine 'CommunicationSessionHandler.restart_sdp' was never awaited
evcc_1   | RuntimeWarning: Enable tracemalloc to get the object allocation traceback

This was happening, because the UDP server and client tasks were not being handled by the wait_until_finished, which cancels properly the tasks in the list of tasks.

logger.exception(
"Could not assign an interface for the UDP "
"server, unable to find network interface card. "
f"{exc}"
)
nic = get_nic(secc_settings.NETWORK_INTERFACE)

interface_idx = socket.if_nametoindex(nic)
join_multicast_group_req = (
Expand Down
7 changes: 7 additions & 0 deletions iso15118/shared/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from typing import Any


class InterfaceNotFound(Exception):
"""
This error is raised when the specified interface is not found under the
available list of interfaces
"""


class NoLinkLocalAddressError(Exception):
tropxy marked this conversation as resolved.
Show resolved Hide resolved
"""
Is thrown if no IPv6 link-local address can be found. Used by TCP/TLS
Expand Down
Loading