Servidor OCPP 1.6 – Simulador de EVSE e Interface de Testes

Entenda os arquivos, as principais rotinas, o fluxo das mensagens e como um EVSE interage com o servidor OCPP.

Este projeto utiliza um EVSE simulado em Node.js para testar a comunicação com um servidor OCPP 1.6. O simulador estabelece uma conexão WSS, executa uma sequência de mensagens OCPP, acompanha as respostas, encaminha eventos para uma interface Web e permite consultar dados do MongoDB.

Figura 1 – Estrutura e relacionamento dos arquivos do simulador OCPP 1.6.

1. Visão geral da arquitetura

O sistema é dividido em quatro elementos: o EVSE simulado, o servidor OCPP 1.6(que não faz parte do post), o MongoDB e a interface Web. No ambiente analisado, o simulador utiliza WSS para chegar ao servidor OCPP na porta 3001. O módulo web-server.js utiliza a porta 3002 para a comunicação com o navegador.

EVSE virtual

        teste-ocpp.js

Representa o carregador. Abre a conexão WSS, envia requisições OCPP, recebe respostas e executa a sequência de teste.

Interface

     web-server.js

Recebe eventos TX/RX do simulador e os encaminha para o navegador por WebSocket. Também importa o leitor do MongoDB.

Banco

     mongo-reader.js

Conecta ao MongoDB, lista coleções e lê até 100 documentos de cada coleção.

2. Como um EVSE funciona

Um EVSE (Electric Vehicle Supply Equipment) é o equipamento que controla e disponibiliza energia para o veículo, fazendo também o monitoramento das condições da sessão. O OCPP atua na comunicação entre o ponto de carga e o sistema central de gerenciamento; ele não é o protocolo que controla diretamente todos os sinais elétricos entre o EVSE e o veículo.

 

Figura 2 – Visão conceitual da interação entre EVSE e servidor OCPP.
Fluxo conceitual:
EVSE → conexão segura → servidor OCPP → processamento/autorização → resposta ao EVSE → atualização da sessão → armazenamento dos dados.

No projeto de teste, o comportamento do EVSE físico é representado pelo arquivo teste-ocpp.js. Isso permite testar a camada de comunicação antes de conectar um carregador físico.

3. Arquivo teste-ocpp.js

Este é o núcleo do simulador. O arquivo utiliza ws, fs e EventEmitter. Ele cria o objeto ocppEvents, utilizado para informar ao servidor Web as mensagens transmitidas e recebidas.

Configuração e identificação do EVSE

Baixar teste-ocpp.js

const WebSocket = require("ws");
const fs = require("fs");
const EventEmitter = require("events");

const ocppEvents = new EventEmitter();

const URL = "wss://192.168.68.114:3001";
const CERT_FILE = "/app/certs/cert.pem";

const CHARGER_ID = "EVSE1";
const ID_TAG = "TAG123";

let ws = null;
let transactionId = null;

function generateUUID() {
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
        /[xy]/g,
        function (c) {
            const r = Math.random() * 16 | 0;
            const v = c === "x"
                ? r
                : (r & 0x3 | 0x8);
            return v.toString(16);
        }
    );
}

Entre os parâmetros definidos estão o endereço WSS, o certificado, o identificador do carregador, o idTag, o número de série, modelo, firmware, conector, veículo e valor do medidor.

3.1 Conexão WSS

A função connect() cria o WebSocket seguro e utiliza o certificado CA. O endereço configurado no arquivo é wss://192.168.68.114:3001.

3.2 Envio das mensagens OCPP

A função sendOcpp(action, payload) gera um UUID, monta a mensagem e a envia pelo WebSocket. O projeto utiliza uma mensagem JSON estruturada com tipo, identificador, ação e payload.

function sendOcpp(action, payload) {
    return new Promise((resolve, reject) => {
        const messageId = generateUUID();

        const ocppMessage = [
            2,
            messageId,
            action,
            payload
        ];

        const message = JSON.stringify(ocppMessage);

        ocppEvents.emit("tx", {
            action: action,
            message: message
        });

        ws.send(message, (err) => {
            if (err) {
                reject(err);
                return;
            }

            resolve(messageId);
        });
    });
}

3.3 Recebimento das respostas

A função receiveOcpp() aguarda uma resposta, converte o JSON recebido e verifica o identificador da mensagem esperada. O código considera messageType === 3 como resposta da requisição correspondente.

4. Comandos OCPP 1.6 usados no simulador

ComandoFunção no testeRotina
BootNotificationApresenta o carregador ao servidor e verifica a aceitação.bootNotification()
HeartbeatSolicita ao servidor o horário atual e mantém o acompanhamento da conexão.heartbeat()
StatusNotificationInforma o estado configurado no simulador.statusNotification()
AuthorizeEnvia o idTag para autorização.authorize()
StartTransactionInicia a sessão e recebe o transactionId.startTransaction()
MeterValuesEnvia o valor de energia simulado.meterValues()
StopTransactionFinaliza a sessão.stopTransaction()
Importante: a tabela acima descreve os comandos efetivamente utilizados pelo arquivo anexado teste-ocpp.js. Ela não representa toda a lista de funcionalidades existentes no OCPP 1.6.

5. Sequência completa do teste

A função main() executa a sequência principal:

Connect
  ↓
BootNotification
  ↓
Heartbeat
  ↓
StatusNotification
  ↓
Authorize
  ↓
StartTransaction
  ↓
MeterValues
  ↓
aguarda 2 segundos
  ↓
StopTransaction
  ↓
fecha conexão

Trecho da sequência principal

Baixar teste-ocpp.js

async function main() {
    try {
        await connect();

        const bootOk = await bootNotification();
        if (!bootOk) {
            throw new Error("BootNotification rejeitado");
        }

        await heartbeat();
        await statusNotification();

        const authorizeOk = await authorize();
        if (!authorizeOk) {
            throw new Error("Authorize rejeitado");
        }

        const startOk = await startTransaction();
        if (!startOk) {
            throw new Error("StartTransaction rejeitado");
        }

        await meterValues();

        await new Promise(resolve =>
            setTimeout(resolve, 2000)
        );

        const stopOk = await stopTransaction();
        if (!stopOk) {
            throw new Error("StopTransaction não aceito");
        }

        ws.close();
    } catch (err) {
        console.error("ERRO NO TESTE:", err);
        if (ws) {
            try { ws.close(); } catch (e) {}
        }
    }
}

6. O transactionId

Um ponto importante do teste é o transactionId. Depois de StartTransaction, o simulador extrai o identificador retornado pelo servidor e o guarda em uma variável. Esse valor é então utilizado nas mensagens relacionadas à transação, especialmente em MeterValues e StopTransaction.

7. O arquivo web-server.js

O web-server.js funciona como uma ponte entre o simulador e o navegador. Ele importa teste-ocpp.js e mongo-reader.js, define a porta 3002 e escuta os eventos tx e rx produzidos pelo EventEmitter.

Recepção dos eventos TX/RX

Baixar web-server.js

const http = require("http");
const WebSocket = require("ws");
const ocpp = require("./teste-ocpp");
const mongoReader = require("./mongo-reader");

const PORT = 3002;

ocpp.ocppEvents.on("tx", (data) => {
    console.log("OCPP TX:", data.message);

    if (browserSocket &&
        browserSocket.readyState === WebSocket.OPEN) {

        browserSocket.send(JSON.stringify({
            type: "OCPP_TX",
            action: data.action,
            message: data.message
        }));
    }
});

ocpp.ocppEvents.on("rx", (data) => {
    console.log("OCPP RX:", data.message);

    if (browserSocket &&
        browserSocket.readyState === WebSocket.OPEN) {

        browserSocket.send(JSON.stringify({
            type: "OCPP_RX",
            message: data.message
        }));
    }
});

Quando ocorre um evento TX, o módulo envia ao navegador um objeto contendo type: "OCPP_TX", a ação e a mensagem. Para RX, envia type: "OCPP_RX" e a mensagem recebida.

8. O arquivo mongo-reader.js

O mongo-reader.js é responsável exclusivamente pela leitura do MongoDB. A conexão é montada a partir das variáveis de ambiente MONGO_HOST, MONGO_PORT, MONGO_USER, MONGO_PASSWORD e MONGO_DATABASE.

Conexão e leitura das coleções

Baixar mongo-reader.js

const { MongoClient } = require("mongodb");

const MONGO_HOST = process.env.MONGO_HOST || "mongodb";
const MONGO_PORT = process.env.MONGO_PORT || "27017";
const MONGO_USER = process.env.MONGO_USER;
const MONGO_PASSWORD = process.env.MONGO_PASSWORD;
const MONGO_DATABASE = process.env.MONGO_DATABASE || "ocpp_2";

const MONGO_URI =
    `mongodb://${encodeURIComponent(MONGO_USER)}:` +
    `${encodeURIComponent(MONGO_PASSWORD)}@` +
    `${MONGO_HOST}:${MONGO_PORT}/` +
    `${MONGO_DATABASE}?authSource=admin`;

async function readCollections() {
    const client = new MongoClient(MONGO_URI);

    try {
        await client.connect();

        const db = client.db(MONGO_DATABASE);
        const collections =
            await db.listCollections().toArray();

        const result = {};

        for (const collection of collections) {
            const documents =
                await db
                    .collection(collection.name)
                    .find({})
                    .limit(100)
                    .toArray();

            result[collection.name] = documents;
        }

        return result;
    } finally {
        await client.close();
    }
}

A rotina readCollections() conecta ao banco, lista as coleções, percorre cada uma delas, consulta até 100 documentos e devolve o resultado em um objeto JavaScript. Ao terminar, fecha a conexão.

9. Como o EVSE interage com o servidor OCPP

Em uma sessão típica, o EVSE inicia a comunicação com o servidor e executa uma sequência de mensagens. No simulador analisado, o fluxo implementado é:

EtapaEVSE / simuladorServidor OCPP
1BootNotificationRecebe os dados do carregador e responde.
2HeartbeatResponde com currentTime.
3StatusNotificationRecebe o estado informado.
4AuthorizeAnalisa o idTag e retorna o status.
5StartTransactionResponde com o transactionId.
6MeterValuesRecebe a medição da sessão.
7StopTransactionProcessa o encerramento da transação.

10. O que acontece em um EVSE real?

Em um EVSE físico, o controlador do carregador acompanha o estado do conector, as condições de segurança, a disponibilidade de energia e os valores do medidor. A comunicação OCPP permite que essas informações sejam compartilhadas com o sistema central.

Assim, podemos separar conceitualmente duas camadas:

Camada elétrica

  • Conexão com o veículo
  • Controle da entrega de energia
  • Monitoramento elétrico
  • Proteções e estados do carregador
  • Medição de energia

Camada OCPP

  • Identificação do carregador
  • Autorização
  • Transações
  • Medições
  • Status
  • Comunicação com o sistema central

11. Estrutura do projeto para download

O pacote deste artigo contém o HTML, os três arquivos JavaScript utilizados pelo simulador e as duas figuras. Os links abaixo funcionam quando todos esses arquivos permanecem no mesmo diretório.

teste-ocpp.js

Cliente OCPP / EVSE simulado.

Baixar código

web-server.js

Servidor Web e ponte TX/RX.

Baixar código

mongo-reader.js

Leitor do MongoDB.

Baixar código

12. Conclusão

O simulador transforma o computador em uma bancada de testes para o servidor OCPP 1.6. O teste-ocpp.js representa o EVSE, o web-server.js acompanha a comunicação e conecta o teste à interface Web, enquanto o mongo-reader.js permite consultar os dados armazenados.

O ponto central é separar a função do EVSE da função do servidor: o EVSE representa o equipamento de campo, enquanto o servidor OCPP coordena a comunicação e as informações da sessão. O simulador permite reproduzir essa comunicação de maneira controlada, tornando mais simples testar, depurar e evoluir o servidor antes da utilização de um carregador físico.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *