Contains common tools for aiding development of applications built on top of the SCN.
git clone [email protected]:smartcharging/scn-tools.git
npm install
It is possible to run a mock E-Mobility Service Provider (MSP) or Charge Point Operator (CPO) with these tools.
Configuration for both MSP and CPO can be found in the src/config
directory. The mock servers will look for a
src/config/config.ts
file which is not provided out of the box (to avoid version control conflicts). Therefore,
before starting a mock server, copy the provided local configuration file and edit to match the desired environment:
cp src/config/config.local.ts src/config/config.ts
See also the example config.volta.ts
config which shows an example of config parameters needed to connect to
the Energy Web Chain's Volta test network. In this case, the scn.node
is fake and the publicIP
of the msp
and cpo
config options should be changed to match the desired public IP of the SCPI party. This is required
because as part of the SCPI credentials handshake, the SCN node needs to fetch supported versions and modules
from the SCPI party.
The mock MSP/CPO, in addition to the provided configuration parameters, need at least two environment variables in order to register the party to the SCN Registry and connect to an SCN Node. Because these variables are only needed to register, they can be discarded on future runs.
These variables are:
TOKEN_A
- the SCPI credentials TOKEN_A which allows the party to connect to an SCN nodeSIGNER_KEY
- the private key used to sign the SCN registry listingSPENDER_KEY
- [optional] the private key which will pay the transaction fee for adding the party to the SCN registry (same asSIGNER_KEY
if not provided)
The TOKEN_A
can be obtained from an SCN node (should be the same as the one in the config). If the API key is known,
a curl request can be made like so:
curl -XPOST localhost:8080/admin/generate-registration-token -H 'Authorization: Token randomkey' -H 'Content-Type: application/json' -d '[{"country_code": "CH", "party_id": "CPO"}]'
Otherwise, TOKEN_A
can be retrieved offline from the chosen SCN node's administrator.
An Ethereum-compliant private key is needed to sign the transaction detailing the SCPI party's SCN registry listing, but it does not need to be the same as the key that sends the transaction to the network.
In any case, it is often simpler to use the same key for signing and paying, especially on test networks where funds to pay for transactions are easily obtainable (and for free).
If running a local network, it is possible to grab a private key from e.g. ganache
which is already funded. If testing
on the Energy Web Chain's Volta test network, you could instead fund your private key's wallet using the faucet.
Once setup, a mock MSP or CPO can be started as follows:
TOKEN_A=ec005952-8c33-4bc0-8032-e07fdc420931 SIGNER_KEY=0x659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63 npm run start-msp
Likewise the npm
script start-cpo
can be used to start and register the CPO server.
If all was successful, something similar to the following should be printed to stdout:
Starting MSP server...
GET /scpi/versions 200 4.932 ms - 147
GET /scpi/versions/2.2 200 1.924 ms - 298
MSP server listening for SCPI requests
If you only want to register the party, without a long-running server process, you may use the --register-only
flag:
TOKEN_A=ec005952-8c33-4bc0-8032-e07fdc420931 SIGNER_KEY=0x659cbb0e2411a44db63778987b1e22153c086a95eb6b18bdf89de078917abc63 npm run start-msp -- --register-only
The server will then exit after registration is complete.
On subsequent runs (assuming the party is already registered), the MSP or CPO can simply be started like so:
npm run start-msp
or
npm run start-cpo
Data provided by the MSP or CPO (tokens, locations, tariffs etc.) exists in src/data
and can be modified if necessary. Each
data file is written in TypeScript, providing SCPI types and programmatic creation of data. It is also completely interoperable
with JSON data:
For example, locations.ts
:
export const locations: ILocation[] = [
{
"country_code": "DE",
"party_id": "CPO",
"id": "abc123",
...
}
]
or
export const locations: ILocation[] = []
for (i = 0; i < 10000000; i++) {
locations.push({
country_code: "DE",
party_id: "CPO",
id: "abc123",
...
})
}
Using the authorization token displayed on MSP or CPO start, requests can be made to the SCN node in SCPI format (version 2.2). The following request examples assume that both MSP and CPO servers have been registered and are awaiting SCPI requests. Therefore, open a terminal session for each server, and an additional one to make the requests. The requests can be used again for requests to additional MSPs/CPOs, though note that the actual results may differ as the SCPI implementation could be different (e.g. a CPO might not push session updates).
You can send the following GET locations request as MSP, replacing the Authrozation TOKEN_C with the MSP's Authorization Token.
curl -s localhost:8080/scpi/sender/2.2/locations -H "Authorization: Token {{TOKEN_C}}" -H "X-Request-ID: 0" -H "X-Correlation-ID: 0" -H "SCPI-From-Country-Code: CH" -H "SCPI-From-Party-Id: MSP" -H "SCPI-To-Country-Code: CH" -H "SCPI-To-Party-Id: CPO"
To parse command line responses, jq
is recommended.
For example, check if the SCPI response was successful (should be 1000):
{{REQUEST}} | jq .status_code
Pretty print response data:
{{REQUEST}} | jq .data
Count objects in response data array:
{{REQUEST}} | jq '.data | length'
Likewise, tariffs can be obtained from the CPO using:
curl -s localhost:8080/scpi/sender/2.2/tariffs -H "Authorization: Token {{TOKEN_C}}" -H "X-Request-ID: 0" -H "X-Correlation-ID: 0" -H "SCPI-From-Country-Code: CH" -H "SCPI-From-Party-Id: MSP" -H "SCPI-To-Country-Code: CH" -H "SCPI-To-Party-Id: CPO"
Charge detail records can also be obtained from the CPO using:
curl -s localhost:8080/scpi/sender/2.2/cdrs -H "Authorization: Token {{TOKEN_C}}" -H "X-Request-ID: 0" -H "X-Correlation-ID: 0" -H "SCPI-From-Country-Code: CH" -H "SCPI-From-Party-Id: MSP" -H "SCPI-To-Country-Code: CH" -H "SCPI-To-Party-Id: CPO"
Sessions can also be obtained from the CPO using:
curl -s localhost:8080/scpi/sender/2.2/sessions -H "Authorization: Token {{TOKEN_C}}" -H "X-Request-ID: 0" -H "X-Correlation-ID: 0" -H "SCPI-From-Country-Code: CH" -H "SCPI-From-Party-Id: MSP" -H "SCPI-To-Country-Code: CH" -H "SCPI-To-Party-Id: CPO"
To start a charging session, use the following example command, containing the driver's SCPI token (the mock CPO will accept any) and the location/evse they will charge at (the mock CPO will reject the request if it does not know the location):
curl -s -XPOST localhost:8080/scpi/receiver/2.2/commands/START_SESSION -H "Authorization: Token {{TOKEN_C}}" -H "X-Request-ID: 0" -H "X-Correlation-ID: 0" -H "SCPI-From-Country-Code: CH" -H "SCPI-From-Party-Id: MSP" -H "SCPI-To-Country-Code: CH" -H "SCPI-To-Party-Id: CPO" -H "Content-Type: application/json" -d '{"response_url": "http://localhost:3001/scpi/sender/2.2/commands/START_SESSION/0", "location_id": "Loc1", "evse_uid": "CH-CPO-S1E100001", "token": {"country_code": "CH", "party_id": "MSP", "uid": "0", "type": "AD_HOC_USER", "contract_id": "0", "issuer": "test MSP", "valid": true, "whitelist": "NEVER", "last_updated": "2019-10-14T15:45:11.353Z"}}'
This is an asynchronous request. In SCPI terms, this means that the CPO will call the sender back via the given
response_url
with additional information.
When sending the request, the initial response should tell us that our request has been accepted:
{
"status_code": 1000,
"data": {
"result": "ACCEPTED",
"timeout": 30
},
"timestamp": "2019-10-15T14:25:05.924Z"
}
When looking at the logs of the MSP server, there is additional information:
POST /scpi/sender/2.2/commands/START_SESSION/0 200 0.696 ms - 59
async command result [START_SESSION 0]: ACCEPTED
This is the asynchronous command result from the CPO, notifying us on the response_url
we have provided, that the
charge point has accepted the session request.
To stop a charging session, we need to obtain its ID.
When looking at the MSP server logs, we can see that there has also been a PUT request made to the SCPI sessions receiver module:
PUT /scpi/receiver/2.2/sessions/CH/CPO/c8cf0ab6-10a2-4c71-88aa-fee1ab29beea 200 5.526 ms - 59
Session c8cf0ab6-10a2-4c71-88aa-fee1ab29beea ACTIVE - 0.0029 kWh
Here, the path parameters /{{country_code}}/{{party_id}}/{{session_id}}
tell us at a glance information about a new
session. The mock CPO is currently configured to send out session updates every 30 seconds.
Now that we have the session ID, we can make the stop session request (make sure to change the session_id
in
the request's body):
curl -s -XPOST localhost:8080/scpi/receiver/2.2/commands/STOP_SESSION -H "Authorization: Token {{TOKEN_C}}" -H "X-Request-ID: 0" -H "X-Correlation-ID: 0" -H "SCPI-From-Country-Code: CH" -H "SCPI-From-Party-Id: MSP" -H "SCPI-To-Country-Code: CH" -H "SCPI-To-Party-Id: CPO" -H "Content-Type: application/json" -d '{"response_url": "http://localhost:3001/scpi/receiver/2.2/commands/STOP_SESSION/0", "session_id": {{SESSION_ID}}}'
The same asynchronous request flow is also present for stopping charging sessions. We can see that the MSP
receives the async result on the response_url
specified, however it also received an additional request:
POST /scpi/receiver/2.2/commands/STOP_SESSION/0 404 4.088 ms - 181
PUT /scpi/receiver/2.2/sessions/CH/CPO/87351731-7a6d-4ce8-9037-df6ae5d0478f 200 2.118 ms - 59
Session 87351731-7a6d-4ce8-9037-df6ae5d0478f COMPLETED - 0.7392 kWh
POST /scpi/receiver/2.2/cdrs 200 2.093 ms - 59
CDR 4545f2d2-3bdc-4156-a87c-b7a0f0c90d92: 0 EUR
This shows us that a new charge detail record has been received with, in this case, a price of 0 EUR.
Using the SCPI command RESERVE_NOW
is also possible, in order to reserve a particular EVSE from now to the given expiry_date
:
curl -X POST http://localhost:8080/scpi/receiver/2.2/commands/RESERVE_NOW -H "Authorization: Token {{TOKEN_C}}" -H "Content-Type: application/json" -H "SCPI-From-Country-Code: CH" -H "SCPI-From-Party-ID: MSP" -H "SCPI-To-Country-Code: CH" -H "SCPI-To-Party-ID: CPO" -H "X-Correlation-ID: 1" -H "X-Request-ID: 1" -d '{"response_url": "http://localhost:3001/scpi/sender/2.2/commands/RESERVE_NOW/0", "token": {"country_code": "CH", "party_id": "MSP", "uid": "1", "type": "APP_USER", "contract_id": "1", "issuer": "some msp", "valid": true, "whitelist": "NEVER", "last_updated": "2019-11-05T10:36:41.778Z"}, "expiry_date": "2019-11-05T18:36:41.778Z", "reservation_id": "1", "location_id": "Loc2", "evse_uid": "CH-CPO-S2E100001"}'
This will block the station until the expiry_date
passes. During this time only the token that was used to reserve the EVSE can charge there. Once charging has started, the reservation will be deleted.
A reservation can be updated by sending a new RESERVE_NOW
request with the reservation id
which should be updated.
A reservation can be cancelled using the CANCEL_RESERVATION
command:
curl -X POST http://localhost:8080/scpi/receiver/2.2/commands/CANCEL_RESERVATION -H "Authorization: Token {{TOKEN_C}}" -H "Content-Type: application/json" -H "SCPI-From-Country-Code: CH" -H "SCPI-From-Party-ID: MSP" -H "SCPI-To-Country-Code: CH" -H "SCPI-To-Party-ID: CPO" -H "X-Correlation-ID: 1" -H "X-Request-ID: 1" -d '{"response_url": "http://localhost:3001/scpi/sender/2.2/commands/CANCEL_RESERVATION/0", "reservation_id": "1"}'
As with other commands, this request is asynchronous.
You can run the CLI entry point with npm start
, however flags need to be declared with an additional --
, e.g.
npm start -- --help
npm start mock -- --cpo
npm run start-msp -- --register-only
You can also use node
or ts-node
. As scn-tools
is written in TypeScript, it needs to be
transpiled into JavaScript first:
npm run build
node dist/index.js --help
If you want to run the TypeScript code directly, you can use ts-node
. It is included as a dependency for local use:
./node_modules/.bin/ts-node src --help
Or it can be installed globally:
npm i -g typescript ts-node
ts-node src --help