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 5 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
File renamed without changes.
File renamed without changes.
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
# the following command 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 wouldnt be able to spawn the evcc start script.
tropxy marked this conversation as resolved.
Show resolved Hide resolved
# @ is used as a separator and allow us to escape '/', so we can substitute the '/' itself
tropxy marked this conversation as resolved.
Show resolved Hide resolved
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
131 changes: 99 additions & 32 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,137 @@ 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.


For option number `2`, the certificates need to be provided. The project includes
a script to help on the generation of -2 or/and -20 certificates. This script
tropxy marked this conversation as resolved.
Show resolved Hide resolved
is under `iso15118/shared/pki/` directory and is called `create_certs.sh`.
tropxy marked this conversation as resolved.
Show resolved Hide resolved
The following command provides a helper for the script usage:

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

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.
tropxy marked this conversation as resolved.
Show resolved Hide resolved

For more information about the MQTT API used by Switch, please contact us.

Finally, the project includes a few configuration variables whose default

---
**IPv6 WARNING**

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
Copy link
Contributor

Choose a reason for hiding this comment

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

No comma before if. "... is fine if the user ..."

communication. This configuration works for both Linux and BSD systems.

However, the usage of an internal `ipv6_net` in docker, does not allow the host
Copy link
Contributor

Choose a reason for hiding this comment

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

docker -> Docker, and no comma after "Docker"

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
Copy link
Contributor

Choose a reason for hiding this comment

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

no comma after "i.e."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just a curiosity on that one: it depends on the style used; American style encloses the abbreviation within commas and the British style does not

host share the same network. This way, the virtual network interface created by
the HomePlugGreenPHY module can be directly accessible by the Docker container and
Copy link
Contributor

Choose a reason for hiding this comment

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

I was told many times by a native English speaker that whenever possible, use the active and not the passive voice. In this case, that would mean instead of "can be accessible by the Docker container" (passive) we should rather write "Docker can directly access".
In German, we often use the passive voice, but in English, it's better to use the other way.

I would write this sentence like this: "This way, Docker can directly access the virtual network interface created by the HomePlugGreenPHY module and it's possible to use the local-link address."
It's just easier to the English-speaking mind.

By the way: is HomePlugGreenPHY a software module you are referring to, or do you mean the actual HW device? If the latter, then we should write it as Home Plug Green PHY

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was told many times by a native English speaker that whenever possible, use the active and not the passive voice. In this case, that would mean instead of "can be accessible by the Docker container" (passive) we should rather write "Docker can directly access".
In German, we often use the passive voice, but in English, it's better to use the other way.

Both are widely used, so it will depend on what you want to emphasise, if the action itself (passive) or the subject of the action (active). In this case, I agree, active voice is a better choice here.

it is possible to use the local-link address.

Currently, `network_mode: host` just works within Linux environments [^5] [^6].
Since Switch team relies mostly on MacOS and since this project is on a
Copy link
Contributor

Choose a reason for hiding this comment

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

Sice Switch team -> Since the Switch team
Also let's avoid using "since" twice in a sentence, you can just delete the second "since"

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
Copy link
Contributor

Choose a reason for hiding this comment

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

No comma before "if"

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
| 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 |
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really need to mention these MQTT variables in this project? If this project will eventually be used by people outside of Switch (and become open-source), with no access to our MQTT implementation, then we might as well omit this here and add the information in our Josev project. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We dont, I removed it already...thanks.

| 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.
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.

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`.
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
Copy link
Contributor

Choose a reason for hiding this comment

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

docker -> Docker

automatically fetch the `.env.dev.docker` one.

The key-values 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 +181,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