Launch both OpenLEADR VTN (top node) and VEN (end node) using Docker for development

; Date: Fri Jun 03 2022

Tags: OpenADR »»»» OpenLEADR »»»» Docker

Let's explore using OpenLEADR to set up a virtual OpenADR network on a local Docker installation. The result will let us edit both the OpenLEADR source code, as well as both VTN and VEN nodes, automatically reloading both VTN and VEN when any files are changed.

To proceed you must have some knowledge of OpenADR. For a quick introduction, take a look at An Introduction to OpenADR - the Automated Demand/Response protocol

OpenLEADR ( (openleadr.org) https://openleadr.org/) is an open source project which is a Python3 implementation of OpenADR 2.0b. Among the open source OpenADR projects, this one is the most up-to-date and uses the latest tools. Unfortunately the documentation is weak on getting started using OpenLEADR.

My preference is to containerize services like this. In this case "Containerize" means to create a Docker container with the environment required to run OpenLEADR. That way the particular environment required to run OpenLEADR is encapsulated in the Docker container, and does not fiddle with the environment on my laptop.

You will of course need to understand Docker, and have Docker installed on your laptop. The simplest route to install Docker is by installing Docker Desktop.

The result we're aiming for is to use Docker Compose to set up an OpenADR network. It will consist of one VTN node, and one or more VEN nodes. It will use a Docker container with Python 3, the packages required to run OpenLEADR, and the nodemon tool which we'll use to automatically restart the VTN or VEN software whenever we change any file.

This will let us run an OpenADR network, freely edit any source files, and have the affected services automatically restart.

The source code discussed in this article is available on GitHub: (github.com) https://github.com/robogeek/openleadr-docker-setup

A Dockerfile to run OpenLEADR applications

Let's start by defining a Docker container, which of course requires building a Dockerfile. The Dockerfile is based on the standard Python 3 container. To that we'll add the packages required for OpenLEADR, the Node.js platform, and nodemon.

FROM python:3

VOLUME /setup
VOLUME /openleadr-python
VOLUME /app

RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
RUN apt-get update && \
        apt-get upgrade -y && \
        apt-get install -y nodejs build-essential curl locales

RUN npm install -g nodemon
COPY requirements.txt /setup/requirements.txt

RUN pip3 install -r /setup/requirements.txt

COPY nodemon.json /setup/nodemon.json

# CMD nodemon --config /setup/nodemon.json
CMD nodemon --watch /app --watch /openleadr-python \
            --exec python3 /app/app.py

The /setup directory will contain setup or configuration files. At the moment we've defined two, /setup/requirements.txt which is to contain a list of Python package names, and /setup/nodemon.json, which is to contain a configuration file for Nodemon.

Next, we install Node.js 18 from the Nodesource repository for Debian operating systems. Then we install Nodemon. Then we copy the setup files to /setup, and use pip3 install to install the Python packages.

The last line uses Nodemon to watch two directories, and to execute python3 /app/app.py. The first directory, /openleadr-python, is to contain the Git repository for the OpenLEADR project. The second, /app, is to contain the application to run, as /app/app.py. If files change in either /app or /openleadr-python, that's a signal we should restart the service. The Nodemon configuration accomplishes this, and we'll see it in action later.

This is designed so we can inject a different command, different configuration files, etc, into the container. We can also set environment variables in the container, and use them to configure the service.

The Dockerfile copies two files into /setup. The first is nodemon.json:

{
    "restartable": "rs",
    "watch": [
        "/openleadr-python",
        "/app"
    ],
    "events": {
        "start": "python -v /app/app.py",
        "restart": "python -v /app/app.py"
    }
}

This is an attempt to do the same as the command line options shown on the CMD line. I wasn't able to get Nodemon to run while using this file, and instead opted to pass the Nodemon configuration on the command line as shown above.

The next is requirements.txt:

termcolor
aiohttp
lxml
signxml
datetime
dataclasses
apscheduler
argparse
xmltodict
jinja2

This is the list of packages required to run the OpenLEADR services.

Finally, there is a file build.sh to build the Docker container:

docker build --tag robogeek/python-openleadr-nodemon:latest .

This way we don't have to waste precious brain cells remembering how to rebuild the container. We've given it a descriptive name, and could upload this to Docker Hub if desired.

To build the container: sh -x build.sh. It should complete correctly.

Setup to prepare to run OpenLEADR in Docker

The directory structure used for this project looks like so:

$ tree .
.
├── docker-compose.yml
├── docker-nodemon
│   ├── build.sh
│   ├── Dockerfile
│   ├── nodemon.json
│   └── requirements.txt
├── testven.py
└── testvtn.py

1 directory, 7 files

The files just described are in a directory named docker-nodemon. In the parent directory we have a docker-compose.yml that's used to build the OpenADR network. The file testvtn.py contains OpenLEADR code to create a VTN node, and testven.py contains OpenLEADR code to build a VEN.

Somewhere you will need to clone the OpenLEADR Github repository:

$ git clone https://github.com/OpenLEADR/openleadr-python.git

Docker Compose file to manage a simple OpenADR network using OpenLEADR

Docker Compose files let us describe one or more Docker containers, and how to connect them together. Compose files are written using YAML, and are a succinct way to configure Docker containers. They can also be used for deployment to cloud services, if desired.

services:
  ...

networks:
  adrnet:

The services tag is where we define the services to be launched. Each is a Docker container, and we'll go over the configuration in a second.

The networks tag lets us create virtual networks. These will let us connect containers together on a private network managed by Docker.

services:
  vtn:
    image: robogeek/python-openleadr-nodemon:latest
    volumes:
      - /home/david/Projects/openadr/openleadr-python:/openleadr-python
      - ./testvtn.py:/app/app.py
    networks:
      - adrnet
    environment:
      - PYTHONPATH=/openleadr-python:/app
      - VTN_NAME=vtn001
    ports:
      - 8080:8080

This is the VTN service. it uses the Docker image we discussed earlier. The networks tag connects the VTN to the network just discussed.

In the volumes tag we mount two things into the running container. The first is the OpenLEADR source code we checked out earlier, which appears as /openleadr-python inside the container. The second is testvtn.py, which contains the source code for our VTN, and is mounted as /app/app.py.

The environment variable PYTHONPATH is used by Python to look up modules that can be accessed with the import command. The openleadr-python workspace is already setup correctly to be used in this way. Adding it to PYTHONPATH lets our code use import openleadr in a natural way. Likewise, adding /app to PYTHONPATH lets our VTN implementation have its own local files to import.

The environment variable VTN_NAME is used in the testvtn.py script used in this demo.

The ports setting exposes the OpenADR service port from the container.

services:
  ...
  ven:
    image: robogeek/python-openleadr-nodemon:latest
    networks:
      - adrnet
    volumes:
      - /home/david/Projects/openadr/openleadr-python:/openleadr-python
      - ./testven.py:/app/app.py
    environment:
      - VEN_NAME=ven123
      - VTN_URL=http://vtn:8080/OpenADR2/Simple/2.0b
      - RESOURCE_NAME=resource
      - PYTHONPATH=/openleadr-python:/app
    depends_on:
      - vtn

This is the VEN service. The setup is roughly the same as for the VTN.

The depends_on tag says to wait for the VTN to start before starting the VEN.

In volumes we mount testven.py as /app/app.py.

The environment configuration includes other configuration settings. Notice that the VTN_URL refers to the vtn service container using a domain name. Docker ensures that the container has a domain name matching its container name, letting us do simple resource discovery.

An OpenLEADR VEN will automatically connect to a VTN using the URL shown here.

Running the OpenADR network using Docker Compose

We don't need to discuss testvtn.py and testven.py. The VTN and VEN implementations you use are up to you. The source repository corresponding to this article includes VTN and VEN implementations.

If you clone the above repository:

$ git clone https://github.com/robogeek/openleadr-docker-setup.git

Then build the Docker container:

$ cd openleadr-docker-setup/docker-nodemon
$ sh -x build.sh

Then run the Docker Compose file:

$ cd ..
$ docker compose up

Notice that this is docker compose and not docker-compose. The latter is the old way to run Docker Compose files, while the first way is the new method. The old way has a - in the middle of the name, while the new way does not. That means the functionality of the old docker-compose command is now built-in to the docker command as docker compose.

Once you've run this, output similar to this will be printed:

$ docker compose up
[+] Running 3/3Network openleadr-docker-setup_adrnet   Created                                                      0.1s
 ⠿ Container openleadr-docker-setup-vtn-1  Created                                                      0.1s
 ⠿ Container openleadr-docker-setup-ven-1  Created                                                      0.1s
Attaching to openleadr-docker-setup-ven-1, openleadr-docker-setup-vtn-1
openleadr-docker-setup-vtn-1  | [nodemon] 2.0.16
openleadr-docker-setup-vtn-1  | [nodemon] to restart at any time, enter `rs`
openleadr-docker-setup-vtn-1  | [nodemon] watching path(s): app/**/* openleadr-python/**/*
openleadr-docker-setup-vtn-1  | [nodemon] watching extensions: py,json
openleadr-docker-setup-vtn-1  | [nodemon] starting `python3 /app/app.py`
openleadr-docker-setup-ven-1  | [nodemon] 2.0.16
openleadr-docker-setup-ven-1  | [nodemon] to restart at any time, enter `rs`
openleadr-docker-setup-ven-1  | [nodemon] watching path(s): app/**/* openleadr-python/**/*
openleadr-docker-setup-ven-1  | [nodemon] watching extensions: py,json
openleadr-docker-setup-ven-1  | [nodemon] starting `python3 /app/app.py`

The first time you run this, there may be additional printout related to downloading Docker images and the like. After that it shows the process of starting the VTN and VEN implementations.

The following lines are logging output from the containers. The first part of these lines is the identifier for the container, and the remainder is the logging output.

The output shown here comes from Nodemon, and describes the configuration it is using. It is scanning the two named directories for any changes. If any of the files changes, it will restart the corresponding service.

There will be additional logging output from the services, depending on the logging messages in your code.

If we make a change to testven.py, this message is printed:

openleadr-docker-setup-ven-1  | [nodemon] restarting due to changes...
openleadr-docker-setup-ven-1  | [nodemon] starting `python3 /app/app.py`

The Nodemon instance managing the VEN notices the change in its /app directory. It will be followed with the startup messages from the VEN.

Likewise, changing testvtn.py gives this message:

openleadr-docker-setup-vtn-1  | [nodemon] restarting due to changes...
openleadr-docker-setup-vtn-1  | [nodemon] starting `python3 /app/app.py`

The Nodemon instance managing the VTN notices the change in its /app directory. This shows that the VTN has changed, and will restart.

Finally, if we change a file in the openleadr-python directory, this message will be printed:

openleadr-docker-setup-vtn-1  | [nodemon] restarting due to changes...
openleadr-docker-setup-ven-1  | [nodemon] restarting due to changes...
openleadr-docker-setup-vtn-1  | [nodemon] starting `python3 /app/app.py`
openleadr-docker-setup-ven-1  | [nodemon] starting `python3 /app/app.py`

Both the VTN and VEN are restarted, because the Nodemon instances managing both are watching the /openleadr-python directory.

Summary

What we have is a simple-to-setup way to use OpenLEADR software to test OpenADR on our laptop. We can edit the OpenLEADR code, or our VTN and VEN implementations, and each automatically reloads as we make changes. The execution environment is encapsulated so it does not affect our laptop. And because it is Docker, we can easily deploy this to a server as needed.

About the Author(s)

(davidherron.com) David Herron : David Herron is a writer and software engineer focusing on the wise use of technology. He is especially interested in clean energy technologies like solar power, wind power, and electric cars. David worked for nearly 30 years in Silicon Valley on software ranging from electronic mail systems, to video streaming, to the Java programming language, and has published several books on Node.js programming and electric vehicles.