Introdução
O OCPP (Open Charge Point Protocol) é um protocolo de comunicação aberto e padronizado que conecta estações de carregamento de veículos elétricos (EVSE) a sistemas de gerenciamento central (CSMS). Ele foi criado para permitir que diferentes fabricantes e provedores trabalhem de forma interoperável, sem depender de soluções proprietárias.
Na versão OCPP 1.6, amplamente utilizada no mercado, a comunicação entre o carregador e o servidor é feita através de WebSocket, um canal bidirecional que permite a troca contínua de mensagens em tempo real. Isso é essencial para funções como:
Autenticação de usuários (ex: cartão RFID ou app).
Início e encerramento de sessões de carregamento.
Monitoramento de consumo de energia.
Atualizações remotas de firmware.
Envio de notificações e diagnósticos.
Para ambientes de produção, recomenda-se fortemente o uso de WebSocket seguro (WSS), que é o WebSocket rodando sobre TLS/SSL. O WSS garante que os dados trafeguem criptografados, evitando problemas como:
Interceptação de dados (ex: IDs de usuários ou dados de cobrança).
Modificação maliciosa de mensagens durante a transmissão.
Ataques do tipo man-in-the-middle.
Em resumo: o OCPP 1.6 com WSS combina padronização, interoperabilidade e segurança, tornando-se a escolha ideal para projetos que envolvem mobilidade elétrica em cenários reais de operação.
Como funciona
Conexão WSS: a interface web envia comando para
client.js
, que conecta ao servidor OCPP.Envio de comandos OCPP: BootNotification, Authorize, StartTransaction, StopTransaction e MeterValues.
Logs em tempo real: mensagens do servidor OCPP são retransmitidas via Socket.IO para o navegador.
Configuração dinâmica: Charger ID pode ser alterado sem reiniciar o Node.js.
Fluxo de comunicação:
usuário → interface HTML → Socket.IO → client.js → WebSocket (servidor OCPP)
mensagens de resposta do servidor OCPP → WebSocket → client.js → Socket.IO → interface HTML
public/index.html:
interface com campos de entrada, botões, logs
funções JS que emitem os eventos corretos para o servidor
Tipos de Mensagem no protocolo OCPP:
Tipo 2: Request / Call
Tipo 3: CallResult
Tipo 4: CallError
Possivelmente Tipo 1 (Heartbeat etc).
Segurança e Certificado SSL no OCPP 1.6
Quando falamos de integração OCPP usando WSS (WebSocket seguro), a comunicação entre a estação de carregamento (EVSE) e o servidor precisa ser criptografada. Isso garante que os dados — como autorizações de usuários, início/fim de transações e medições de energia — não possam ser interceptados ou alterados por terceiros.
No projeto, isso é feito configurando um certificado SSL no cliente, carregado pelo client.js
:
Pontos importantes:
Uso de certificados válidos
Sempre que possível, utilize certificados emitidos por uma autoridade certificadora confiável (ex: Let’s Encrypt). Assim, seu cliente pode confiar no servidor sem ajustes manuais.Certificados autoassinados
Para testes locais, você pode usar um certificado autoassinado (cert.pem
). Nesse caso, será necessário fornecer explicitamente o arquivo.pem
no cliente. Em produção, essa prática não é recomendada, pois expõe a comunicação a riscos de ataques “man-in-the-middle”.rejectUnauthorized: true
Essa opção garante que o cliente só aceite conexões com certificados válidos. Definir comofalse
pode facilitar testes, mas abre brechas graves de segurança.Proteção de dados sensíveis
Todas as mensagens OCPP carregam informações importantes, como IDs de usuários (tags RFID) e registros de cobrança. O uso de WSS com SSL garante que esses dados estejam protegidos.Rotação e validade
Certificados têm prazo de validade. Certifique-se de renovar antes do vencimento e, se possível, automatizar a rotação (ex: usando o Certbot para Let’s Encrypt).
Estrutura do Projeto
cert.pem: necessário para validar a conexão WSS.
client.js: gerencia a conexão OCPP e integra o Socket.IO.
index.html: interface web para enviar comandos e exibir logs em tempo real.
client.js
const fs = require("fs");
const path = require("path");
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const WebSocket = require("ws");
// ==========================
// Configurações OCPP Client
// ==========================
const SERVER_URL = "wss://0.0.0.0:3000"; // seu servidor OCPP no teste rodei no IP add 192.168.68.114:3000
let CHARGE_BOX_ID = "EVSE0200"; // pode ser alterado pela UI
const CERT_PATH = path.join(__dirname, "cert.pem");
let ws; // conexão WebSocket com CSMS
let messageCounter = 1;
// ==========================
// Funções auxiliares
// ==========================
function generateUniqueId() {
return `${Date.now()}-${messageCounter++}`;
}
// ==========================
// Funções OCPP
// ==========================
function connectOcpp(io) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.close();
}
const caCert = fs.readFileSync(CERT_PATH);
const ocppUrl = `${SERVER_URL}/${CHARGE_BOX_ID}`;
ws = new WebSocket(ocppUrl, ["ocpp1.6"], {
ca: caCert,
rejectUnauthorized: true
});
ws.on("open", () => {
io.emit("log", `✅ Conectado ao CSMS via WSS: ${ocppUrl}`);
});
ws.on("message", (data) => {
try {
const msg = JSON.parse(data.toString());
const msgType = msg[0];
if (msgType === 3) {
// CallResult
io.emit("log", `⬅️ CallResult recebido: ${JSON.stringify(msg)}`);
} else if (msgType === 4) {
// CallError
io.emit("log", `❌ CallError recebido: ${JSON.stringify(msg)}`);
} else {
io.emit("log", `⬅️ Mensagem inesperada: ${JSON.stringify(msg)}`);
}
} catch (err) {
io.emit("log", `❌ Erro ao parsear mensagem: ${err.message}`);
}
});
ws.on("close", () => {
io.emit("log", "🔌 Conexão OCPP encerrada");
});
ws.on("error", (err) => {
io.emit("log", `❌ Erro OCPP: ${err.message}`);
});
}
function sendOcppMessage(action, payload, io) {
if (!ws || ws.readyState !== WebSocket.OPEN) {
io.emit("log", "⚠️ Não conectado ao servidor OCPP");
return;
}
const msgId = generateUniqueId();
const msg = [2, msgId, action, payload];
ws.send(JSON.stringify(msg));
io.emit("log", `➡️ Enviado: ${JSON.stringify(msg)}`);
}
// ==========================
// Express + Socket.IO (UI)
// ==========================
const app = express();
const server = http.createServer(app);
const io = new Server(server);
app.use(express.static(path.join(__dirname, "public")));
io.on("connection", (socket) => {
socket.emit("log", "🌐 Cliente conectado à interface web");
socket.on("setChargerId", (id) => {
CHARGE_BOX_ID = id;
socket.emit("log", `🔧 ChargerId configurado: ${CHARGE_BOX_ID}`);
});
socket.on("connectOcpp", () => connectOcpp(io));
socket.on("bootNotification", (params) => {
sendOcppMessage("BootNotification", {
chargerId: params.model || CHARGE_BOX_ID,
chargePointModel: params.model || "modelX",
chargePointVendor: params.vendor || "vendorX",
chargeBoxSerialNumber: CHARGE_BOX_ID,
firmwareVersion: params.firmware || "1.0"
}, io);
});
socket.on("authorize", (params) => {
sendOcppMessage("Authorize", { idTag: params.idTag || "TAG123" }, io);
});
socket.on("startTransaction", (params) => {
sendOcppMessage("StartTransaction", {
connectorId: parseInt(params.connectorId) || 1,
idTag: params.idTag || "TAG123",
meterStart: parseInt(params.meterStart) || 0,
timestamp: new Date().toISOString()
}, io);
});
socket.on("stopTransaction", (params) => {
sendOcppMessage("StopTransaction", {
transactionId: parseInt(params.transactionId) || 1,
meterStop: parseInt(params.meterStop) || 100,
timestamp: new Date().toISOString()
}, io);
});
socket.on("heartbeat", () => {
sendOcppMessage("Heartbeat", {}, io);
});
});
// ==========================
// Start servidor web
// ==========================
const PORT = 8080;
server.listen(PORT, () => {
console.log(`🚀 Interface disponível em http://localhost:${PORT}`);
});
Interface Web (index.html
)
Permite enviar comandos OCPP e visualizar logs em tempo real:
<!DOCTYPE html>
<html lang=”pt-br”>
<head>
<meta charset=”UTF-8″ />
<title>Cliente OCPP 1.6</title>
<script src=”/socket.io/socket.io.js”></script>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f0f0f0; }
h1 { color: #333; }
#logs { background: black; color: lime; padding: 10px; height: 300px; overflow-y: scroll; }
button { margin: 5px; padding: 10px; }
input { padding: 5px; margin: 5px; width: 250px; }
fieldset { margin: 10px 0; padding: 10px; }
label { display: inline-block; width: 180px; }
</style>
</head>
<body>
<h1>Cliente OCPP 1.6</h1>
<fieldset>
<legend>Configuração</legend>
<label>chargerId: </label>
<input type=”text” id=”chargerId” value=”EVSE0200″ />
<button onclick=”setChargerId()”>Definir</button>
<button onclick=”connectOcpp()”>Conectar</button>
</fieldset>
<fieldset>
<legend>BootNotification</legend>
<label>chargerId: </label><input type=”text” id=”chargerId” value=”EVSE0200″ /><br>
<label>Vendor: </label><input type=”text” id=”vendor” value=”BTech” /><br>
<label>Model: </label><input type=”text” id=”model” value=”BTech ModelY” /><br>
<label>ChargeBox Serial Number: </label><input type=”text” id=”chargeBoxSerialNumber” value=”BTech0001″ /><br>
<label>ChargePoint Serial Number: </label><input type=”text” id=”chargePointSerialNumber” value=”CP123456″ /><br>
<label>Firmware Version: </label><input type=”text” id=”firmware” value=”1.0″ /><br>
<button onclick=”sendBoot()”>Enviar BootNotification</button>
</fieldset>
<fieldset>
<legend>Authorize</legend>
<label>idTag: </label><input type=”text” id=”authIdTag” value=”TAG123″ />
<button onclick=”sendAuthorize()”>Enviar Authorize</button>
</fieldset>
<fieldset>
<legend>StartTransaction</legend>
<label>ConnectorId: </label><input type=”number” id=”connectorId” value=”1″ /><br>
<label>idTag: </label><input type=”text” id=”startIdTag” value=”TAG123″ /><br>
<label>MeterStart: </label><input type=”number” id=”meterStart” value=”0″ /><br>
<button onclick=”sendStart()”>Enviar StartTransaction</button>
</fieldset>
<fieldset>
<legend>StopTransaction</legend>
<label>TransactionId: </label><input type=”number” id=”transactionId” value=”1″ /><br>
<label>MeterStop: </label><input type=”number” id=”meterStop” value=”100″ /><br>
<button onclick=”sendStop()”>Enviar StopTransaction</button>
</fieldset>
<fieldset>
<legend>Outros</legend>
<button onclick=”sendHeartbeat()”>Enviar Heartbeat</button>
</fieldset>
<h3>Logs</h3>
<div id=”logs”></div>
<script>
const socket = io();
// Logs
socket.on(“log”, (msg) => {
const logs = document.getElementById(“logs”);
logs.innerHTML += msg + “<br>”;
logs.scrollTop = logs.scrollHeight;
});
// Definir ChargerId localmente
function setChargerId() {
const id = document.getElementById(“chargerId”).value;
socket.emit(“setChargerId”, id);
log(`ChargerId definido: ${id}`);
}
function connectOcpp() {
socket.emit(“connectOcpp”);
log(“Tentando conectar ao servidor OCPP…”);
}
// BootNotification com chargerId
function sendBoot() {
const messageId = Date.now() + “-1”; // gera ID único
const payload = {
chargerId: document.getElementById(“chargerId”).value,
chargePointVendor: document.getElementById(“vendor”).value,
chargePointModel: document.getElementById(“model”).value,
chargeBoxSerialNumber: document.getElementById(“chargeBoxSerialNumber”).value,
chargePointSerialNumber: document.getElementById(“chargePointSerialNumber”).value,
firmwareVersion: document.getElementById(“firmware”).value
};
const message = [2, messageId, “BootNotification”, payload];
socket.emit(“bootNotification”, message); // envia no formato array OCPP 1.6
}
function sendAuthorize() {
const payload = {
action: “Authorize”,
chargerId: document.getElementById(“chargerId”).value,
idTag: document.getElementById(“authIdTag”).value
};
socket.emit(“authorize”, payload);
log(“Authorize enviado: ” + JSON.stringify(payload));
}
function sendStart() {
const payload = {
action: “StartTransaction”,
chargerId: document.getElementById(“chargerId”).value,
connectorId: document.getElementById(“connectorId”).value,
idTag: document.getElementById(“startIdTag”).value,
meterStart: document.getElementById(“meterStart”).value
};
socket.emit(“startTransaction”, payload);
log(“StartTransaction enviado: ” + JSON.stringify(payload));
}
function sendStop() {
const payload = {
action: “StopTransaction”,
chargerId: document.getElementById(“chargerId”).value,
transactionId: document.getElementById(“transactionId”).value,
meterStop: document.getElementById(“meterStop”).value
};
socket.emit(“stopTransaction”, payload);
log(“StopTransaction enviado: ” + JSON.stringify(payload));
}
function sendHeartbeat() {
const payload = {
action: “Heartbeat”,
chargerId: document.getElementById(“chargerId”).value
};
socket.emit(“heartbeat”, payload);
log(“Heartbeat enviado”);
}
function log(msg) {
const logs = document.getElementById(“logs”);
logs.innerHTML += `[${new Date().toLocaleTimeString()}] ${msg}<br>`;
logs.scrollTop = logs.scrollHeight;
}
</script>
</body>
</html>
Testando localmente
Coloque
cert.pem
na raiz do projeto.Execute o servidor Node.js:
Acesse
http://localhost:8080
.Defina o Charger ID, clique em “Conectar WSS” e teste os comandos OCPP.
Deixe um comentário