This software implements an EV roaming E-Mobility Service Provider: the other end of every OCPI roaming agreement, the thing the charge point operators are peered with and push their locations, tariffs, sessions and charge detail records into, with a web interface in front of it. What it is and what it can be told lives in libs/EMSP; this repository is the command line that starts it and the submodules it is built from.
The libraries it is built from are submodules, so they have to come along:
git clone --recurse-submodules <this repository>
If you already cloned it without them:
git submodule update --init --recursive
They are fetched from GitHub over https, so nothing but git is needed - no account, no key.
On Windows, turn long paths on first:
git config --global core.longpaths true
The deepest file in the submodules is well over 140 characters below the clone
root, so under the classic 260-character limit the root has little room to live
in. D:\src\EMSP is fine; a checkout somewhere below
C:\Users\<you>\AppData\Local\Temp\... is not, and the clone fails halfway
through a submodule with Filename too long rather than at the start.
Per clone instead of globally: git clone -c core.longpaths=true ....
dotnet build EMSPCLI.slnx
dotnet run --project EMSPCLI
The build needs the .NET 10 SDK and Node.js: the web interface is built by npm
and embedded into the assembly, so the EMSP is one thing to deploy. dotnet build -p:SkipFrontendBuild=true leaves the npm step out and reuses whatever is
in libs/EMSP/EMSP/Frontend/dist.
At the first start there are no accounts, so the EMSP makes one up for the user
root, keeps its hash with the other accounts below accounts/ beside the
solution and prints the password once. Then open http://127.0.0.1:2355/ - a
welcome that shows a driver the way to sign up and root the way in - and
sign in. Signing in happens at Hermod's HTTPExt API, mounted under /ext - the
same door the other components use, which is what lets one sign-in cover
several of them when they share a server. What an account may do there is a
matter of its roles - a driver, the operator (emsp), a viewer, the
administrators - and the configuration file may add roles of its own; see
libs/EMSP.
Recommended for the first start: give root your own SSH key with it, so
that you can type at the EMSP over SSH from the start - see
Typing at it over SSH:
dotnet run --project EMSPCLI -- --authorize-ssh-key root=C:\Users\you\.ssh\id_ed25519.pub
Without it, the first start makes up a key pair for root and prints its
private key once, below the password.
A roaming partner is given one URL, http://127.0.0.1:2355/ext/versions, and
finds everything else of OCPI from it. Who this EMSP is - DE-GDF unless
configuration.json beside the solution says otherwise - and which OCPI
versions it offers are read from that file once, at the start. The partners it
is peered with, the tokens it issued and what the partners pushed are kept by
the OCPI library in files of its own below ocpi/ beside it, where the web
interface puts them.
A driver signs up at http://127.0.0.1:2355/signup and asks the EMSP for a
contract certificate: the browser makes the key pair and never lets it go,
the EMSP signs a certificate to a fresh eMAID below its own MO root, and the
driver downloads a PKCS#12 for the vehicle and mo-root.pem for whoever has
to believe it - the vehicle, and the charge point operator. The MO root and
its two sub-CAs are made at the first start and kept below pki/ beside the
solution, together with every contract issued; the console names the file to
hand out at every start. How this works, and what a contracts section of
the configuration file may say, is in libs/EMSP.
A driver lands in the organization EVDrivers, apart from root's, and
finds their own pages in the menu: Charging - the sessions and charge
detail records the partners pushed with one of their cards or contracts in
them - RFID cards, Contracts and Profile. A card a driver enters
by its UID charges once the operator lets it in on the same RFID cards page,
as a token on every OCPI version; its driver may block it and remove it. On
the Profile page a driver changes their name, e-mail address and password,
and deletes their account, their contracts and cards with it. The cards are
kept in cards/index.json beside the solution; see
libs/EMSP.
On Keys & tickets a driver has long-term account keys certified - made in
the browser, saved with their certificate as one password-protected file,
good for two years - and has charging tickets signed with them: a CBOR
COSE_Sign that lets whoever holds the ticket's own key charge at a CPO who
believes this EMSP, within the ticket's limits, without saying who the driver
is. The EMSP takes the driver's signature off and signs the ticket itself; a
CPO believes it by pki/tickets/ticket_issuer.cert.pem, which the console
names at the first start. See
libs/EMSP.
dotnet run --project EMSPCLI -- --help lists the rest: --port, --any,
--accounts <dir>, --frontend <dir>, --config <file>, --verbose,
--quiet, --no-trace, --log-file <dir>, --no-log-file, and the four
of the certificate store below. They are every node's switches, read by
WWCP_Node, as is what the console says once the EMSP is up.
While working on the web interface, run npm run watch in
libs/EMSP/EMSP/Frontend and start the EMSP with --frontend libs/EMSP/EMSP/Frontend/dist: a reload in the browser then shows the change,
without rebuilding the C# side.
Everything that happens is written three times over, because the three answer
different questions. The console shows what is going on to whoever is
watching, at the level --verbose and --quiet choose. The Logs page
keeps the last two thousand entries for whoever asks, and loses them when the
process ends. And logs/ beside the solution keeps one file per day,
emsp-2026-09-25.log, every entry down to the debug ones, for the afternoon
somebody asks what happened last night - --log-file <dir> puts it elsewhere,
--no-log-file leaves it out, and nothing in it is ever deleted. Which
directory it is, the start says under log files.
Below it, metrological/ is the EMSP's log book: what bears on the time it
stamps things with and on what it trusts. Every start, every synchronisation
with what each time server answered, what was news about a time server's
certificate, every change of its time servers and of its certificates, one
line after the other, each pointing back at the one before and signed with a
key kept beside them. A file per day, never thinned out. Without log files
there is no log book either.
Once it is up, the console is a prompt rather than a place that only scrolls:
EMSP> syncNTS
succeeded after 809 ms: 4 of 4 server(s) answered (2 required), offset +1015.4 ms, spread 2.0 ms
ptbtime1.ptb.de +1014.8 ms, round trip 50.7 ms, key exchange new
ptbtime2.ptb.de +1015.9 ms, round trip 50.8 ms, key exchange new
ptbtime3.ptb.de +1014.9 ms, round trip 50.6 ms, key exchange new
ptbtime4.ptb.de +1016.8 ms, round trip 50.5 ms, key exchange new
help lists what can be typed, quit leaves, Tab completes and ↑
walks back through what was typed before. syncNTS is Sync now on the
NTS page, typed: the same group of time servers is asked, the same entries
go into the log, and the same result is left behind for the page to show. The
one entry that differs says who asked - the page names the account that
pressed the button, the prompt says it was somebody at the command line.
syncNTS <time server> is that server's Test button: every step with when
it happened, the TLS certificate chain down to its root CA among them. Only a
server of this EMSP is tested, and Tab offers them. Neither steps the clock.
The log keeps writing while you type, from whichever thread did the thing it is reporting, and your half-typed line survives it: the line is taken off the screen, the entry is written whole, and the line comes back with the cursor where it was. A line wider than the console is shown through a window onto it.
Where there is no terminal - from a script, under a service manager, in CI, or
with the output going into a file or through | tee - there is no prompt, and
the EMSP runs until it is stopped: by Ctrl+C, or by the SIGTERM a service
manager stops it with, which shuts it down just as Ctrl+C does.
The same prompt is served over SSH, on port 22355 — twenty thousand above the
web interface's — and on the addresses the web interface listens on: the
loopback, or every address with --any. Nothing else is: no shell of the
machine, no files, no tunnels. --ssh-port moves it, --no-ssh switches it
off.
Configuration > SSH server does the same while the EMSP runs, at once -
switched on or off, its port, whether passwords open it - and keeps it in the
configuration file for the next start; a switch on the command line still wins,
and the page says so. It shows the host key as a client shows it the first
time and as a known_hosts line, who is connected, and the keys every account
may sign in with. Reading it is ssh:read, changing it ssh:edit, which only
the administrators have.
Whoever signs in is an account of the EMSP, under its name, with a key of its
own. The first start makes root; give it your public key with that very
start - the way recommended:
dotnet run --project EMSPCLI -- --authorize-ssh-key root=C:\Users\you\.ssh\id_ed25519.pub
The private key then stays on your machine, and no console ever shows it.
A first start without --authorize-ssh-key root=... makes up a key pair for
root instead and prints its private key once, below the password in the
first-start box: save the lines from -----BEGIN OPENSSH PRIVATE KEY----- to
the END line as a file only you can read, and sign in with
ssh -i <file> -p 22355 root@127.0.0.1, or import the file in PuTTYgen for
PuTTY. Like the password it is kept nowhere - but a console may be kept, by a
service's journal or a redirected output; replace it with your own key and
take it out. --authorize-ssh-key also works at any later start.
An OpenSSH .pub goes in as it is, and so does what PuTTYgen saves with Save
public key. The key is kept with the account, beside its password, and the
sshKeys command manages it - at the console, or over SSH for the account
signed in:
sshKeys root
sshKeys root add ssh-ed25519 AAAA... you@laptop
sshKeys root remove SHA256:abc
remove takes the fingerprint sshKeys lists, or enough of its beginning, and
locks that key out at once. apiKeys does the same for the account's API keys,
and shows a new one once, when it is made. In the browser, the name at the foot
of the menu opens the account's own page, where whoever is signed in adds their
SSH keys and API keys, switches them off and on, and removes them.
Then, to sign in:
ssh -p 22355 root@127.0.0.1
or, in PuTTY, host 127.0.0.1, port 22355, Connection → Data → Auto-login
username root, and the private key under Connection → SSH → Auth →
Credentials. The first time, PuTTY asks whether to trust the EMSP's host key:
the banner prints its fingerprint under SSH, to compare it with.
Everything works as at the console — Tab, the history, the log above the line
being typed — with three differences. quit, exit and Ctrl+D leave the
session, and the EMSP keeps running. The account may do what its roles let it
do on the web interface, and the log names it: "'root' at the command line over
SSH asked this EMSP to synchronise its time.", tagged cli and ssh. And the
session's log starts at the console's level and is its own: log debug shows
everything here, log off nothing, for this session alone. who says who else
is signed in.
Everything this EMSP believes, everything it presents and every server it
recognises lives in one store, certificates/ beside the configuration file -
so beside the solution unless --config says otherwise - and is managed on the
Certificates page, certificates alone, and the Identities page, who the
EMSP is as a client with its private key, or from the command line. The store
is the directory: one file per certificate below it, and an index.json
recording what a file cannot say about itself: what somebody calls it, whether
it is switched on and - for a TLS root or a server certificate - what it is
kept for. So a store copied to another machine arrives complete, and a lost
index costs labels, switches and usages rather than certificates.
A tlsRoot says which time server and which name server over TLS or HTTPS
may be believed, beside the roots of the machine the EMSP runs on - and is told
what it is for, nts, dns or both, because a root kept for the name servers
alone vouches for no time. A tlsServer is a server's own certificate, kept
so that the server can be held to it by its fingerprint. Holding a server to a
certificate or a root is said on the NTS client and DNS client pages:
a server's dialog takes the fingerprints, offers the one it showed last and
the ones the store keeps for it, and says what a mismatch comes to and whether
it is held to what it is first believed with. What every server was last
believed with is kept in known-servers.json beside the configuration file -
fingerprints and nothing else - so that another certificate is noticed where a
server is held to none.
The store keeps the three roots of Plug & Charge as well - v2gRoot, moRoot
and oemRoot, kept apart because one bag of roots would let an OEM root vouch
for a contract - and a clientRoot and a tlsIdentity, who the EMSP is as a
client, which nothing in the EMSP uses yet. What only a vehicle holds is
refused. The MO root this EMSP signs its contracts below is not in the store:
it is kept with its private key below pki/, see above.
dotnet run --project EMSPCLI -- --import-certificate tlsRoot=our-clocks-root.pem --list-certificates
A root is believed as soon as it is in - a TLS root or a server certificate for
every use, until the Certificates page says what it is for.
--list-certificates prints every handle, and for a TLS root or a server
certificate what it is kept for; --certificates <dir> points the EMSP at
another store, a relative directory measured from where the EMSP is started.
PEM, DER and PKCS#12 all go in, and a protected PKCS#12 is opened with
--certificate-password <pw> or, better, EMSP_CERT_PASSWORD - once, and
the password is not kept.
The store holds private keys unencrypted: a PKCS#12 is opened with its password once, at import, and written back without one. The file system is what guards them, and the EMSP says so at every start and at every import. Reading the store is for every role but a driver; changing it is the administrators', unless the configuration file says otherwise.
EMSPCLI/ |
the command line: switches, what the console says at a start, and the prompt in CLI/ - the node's, with the commands every node has, syncNTS among them |
libs/EMSP/EMSP/ |
the EMSP itself - its sections of the configuration file, its roles, its routes on the node's JSON API, its OCPI bindings, its web interface |
libs/EMSP/EMSP/Frontend/ |
the web interface: TypeScript and SCSS, bundled by webpack |
libs/EMSP/EMSP/Contracts/ |
the MO root and its sub-CAs, the signing of contracts, the eMAID and its check digit |
libs/EMSP/EMSPTests/ |
what an EMSP does when a browser, a driver or a CPO talks to it |
libs/WWCP_Node/ |
the node below the EMSP: the log, the configuration file, name resolution and the time, the certificate store, the accounts, the web server and the JSON API every node answers - what an EMSP has in common with a vehicle and a charging station |
libs/WWCP_OCPI/ |
the protocol: OCPI 2.1.1, 2.2.1 and 2.3.0 |
libs/WWCP_POI/ |
what WWCP_Core and WWCP_OCPI say about charging infrastructure alike: the identifications of operators, pools, stations and EVSEs, their status values, the transparency software |
libs/WWCP_ISO15118/ |
the ISO 15118 certificate profiles the MO root is built to |
.github/workflows/ |
what runs on every push, and what runs at night |
The command line is this program's vocabulary and nothing else. What an EMSP
is, and what it does, lives in libs/EMSP.
This software is Open Source under the Affero GPL 3.0 license. We appreciate your participation in this ongoing project, and your help to improve it and the e-mobility ICT in general. If you find bugs, want to request a feature or send us a pull request, feel free to use the normal GitHub features to do so. For this please read the Contributor License Agreement carefully and send us a signed copy or use a similar free and open license.