🔹 O que é Node.js?
O Node.js é um runtime JavaScript baseado no motor V8 do Google Chrome, ideal para construir aplicações de rede escaláveis.
Com ele é possível desenvolver APIs, servidores HTTP/HTTPS, WebSockets, aplicações em tempo real e muito mais.
Principais vantagens:
Assíncrono e orientado a eventos → ótimo para conexões simultâneas.
Multiplataforma → roda em Linux, Windows, macOS e até em dispositivos como Raspberry Pi.
Ecossistema NPM → milhares de pacotes prontos para uso.
🔹 Instaladores do Node.js
Você pode instalar o Node.js de várias formas:
Linux (Debian/Ubuntu)
Windows
Baixar o instalador no site oficial: https://nodejs.org
Instalar e usar via PowerShell ou CMD.
macOS
Instalação recomendada (NVM – Node Version Manager)
Verificar instalação:
🔹 Configurando a Aplicação
O arquivo do servidorOCPP.js está listado abaixo. Agora vamos preparar tudo para rodá-lo.
1. Criar pasta do projeto
2. Criar package.json
3. Instalar dependências necessárias
O
fs
ehttps
já fazem parte do Node.js, não precisam ser instalados.
🔹 Criando certificados TLS (self-signed)
Como o servidor usa HTTPS + WSS, precisamos de certificados:
Isso gera:
key.pem
→ chave privadacert.pem
→ certificado público
🔹 Executando o Servidor
Para iniciar:
Se tudo estiver certo, você verá no log algo como:
🔹 Estrutura final do projeto
✅ Agora está concluido, um servidor OCPP1.6 com WebSocket seguro rodando em Node.js, com banco de dados leve NeDB para armazenar chargers e transações.
Código abaixo (servidorOCPP.js) do Servidor OCPP1.6 é uma simplificação, um ponta pé inicial. Rotinas para validação da autenticação(Autorização) devem ser elaboradas. Uma migração para o banco de dados MongoDB é desejável. Foi implementado dessa maneira, para poder rodar em um Raspberry Pi 3, em uma rede local com endereço IP fixo 192.168.68.114:3000, com TLSv1.2 autoassinado, dada as limitações do hardware onde está rodando o Servidor.
// servidorOCPP.js
const fs = require('fs');
const https = require('https');
const WebSocket = require('ws');
const Datastore = require('nedb-promises');
// --- TLS: certificado e chave privada ---
const serverOptions = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
requestCert: false,
rejectUnauthorized: false,
minVersion: 'TLSv1.2',
maxVersion: 'TLSv1.2'
};
// --- Criar servidor HTTPS e WSS ---
const httpsServer = https.createServer(serverOptions);
const wss = new WebSocket.Server({ server: httpsServer });
// --- NeDB Datastores ---
const Charger = Datastore.create({ filename: 'charger.db', autoload: true });
const Transaction = Datastore.create({ filename: 'transaction.db', autoload: true });
// --- Função auxiliar para log ---
function logToFile(message) {
const timestamp = new Date().toISOString();
const logMsg = `[${timestamp}] ${message}\n`;
console.log(message);
fs.appendFileSync('server.log', logMsg, { encoding: 'utf8' });
}
// --- Geração de TransactionId ---
const generateTransactionId = (() => {
let counter = 0;
return () => {
counter++;
return `${Date.now()}${counter}`;
};
})();
// --- Enviar erro ---
const sendErrorResponse = (ws, errorCode, errorMessage) => {
const response = { action: 'Error', errorCode, errorDescription: errorMessage };
ws.send(JSON.stringify(response));
};
function parseOcppMessage(message) {
try {
// garante que a mensagem ehJSON valida
const msg = JSON.parse(message);
if (!Array.isArray(msg)) {
throw new Error("Mensagem OCPP invalida (nao eh array)");
}
const messageType = msg[0]; // 2=Request, 3=Response, 4=Error
const messageId = msg[1];
let action = null;
let payload = null;
if (messageType === 2) {
// Request => [2, messageId, action, payload]
action = msg[2];
payload = msg[3];
} else if (messageType === 3) {
// Response => [3, messageId, payload]
payload = msg[2];
} else if (messageType === 4) {
// Error => [4, messageId, errorCode, payload]
action = "Error";
payload = { errorCode: msg[2], details: msg[3] };
}
return {
messageType,
messageId,
action,
payload
};
} catch (err) {
console.error("Erro ao parsear mensagem OCPP:", err);
return null;
}
}
// --- WSS Connection ---
wss.on('connection', function connection(ws) {
logToFile('✔ Novo cliente conectado');
ws.chargerId = null;
ws.on('message', async function incoming(message) {
try {
console.log("Mensagem Inteira recebida de cliente");
const msgOCPP = parseOcppMessage(message);
console.log(msgOCPP);
console.log("action:"+msgOCPP.action);
switch (msgOCPP.action) {
case 'BootNotification':
const {
chargerId,
chargePointModel,
chargePointVendor,
chargeBoxSerialNumber,
firmwareVersion
} = msgOCPP.payload;
ws.chargerId = chargerId;
await bootNotification(ws, msgOCPP.payload);
break;
case 'StatusNotification':
await statusNotification(msgOCPP.payload);
break;
case 'Heartbeat':
await heartbeat(ws);
break;
case 'Authorize':
const {
idTag
} = msgOCPP.payload;
await authorize(ws, msgOCPP.payload);
break;
case 'StartTransaction':
await startTransaction(ws, msgOCPP.payload.idTag);
break;
case 'StopTransaction':
await stopTransaction(ws, msgOCPP.payload.transactionId);
break;
case 'MeterValues':
await meterValues(ws, msgOCPP.payload.transactionId, msgOCPP.payload.values);
break;
default:
sendErrorResponse(ws, 'UnknownAction', 'Ação desconhecida');
break;
}
} catch (error) {
console.error('Erro ao processar mensagem:', error);
sendErrorResponse(ws, 'UnknownError', 'Erro desconhecido');
}
});
ws.on('close', function close() {
logToFile('❌ Cliente desconectado');
});
});
// --- BootNotification ---
const bootNotification = async (ws, msg) => {
const { chargerId, chargePointModel, chargePointSerialNumber, firmwareVersion } = msg;
const existingCharger = await Charger.findOne({ chargerId });
if (!existingCharger) {
await Charger.insert({
chargerId,
chargePointModel,
chargePointSerialNumber,
firmwareVersion,
status: 'Connected',
currentTime: new Date().toISOString()
});
logToFile('Charger registrado: ' + chargerId);
}
const response = {
currentTime: new Date().toISOString(),
interval: 60,
status: 'Accepted'
};
ws.send(JSON.stringify({ action: 'BootNotification', ...response }));
};
// --- StatusNotification ---
const statusNotification = async (msg) => {
const { chargerId, status } = msg;
await Charger.update({ chargerId }, { $set: { status } });
logToFile(`Status atualizado: ${chargerId} -> ${status}`);
};
// --- Heartbeat ---
const heartbeat = async (ws) => {
const response = { currentTime: new Date().toISOString() };
ws.send(JSON.stringify({ action: 'Heartbeat', ...response }));
};
// --- Authorize ---
const authorize = async (ws, idTag) => {
const idTagInfo = { status: 'Accepted' }; // lógica de autorização pode ser implementada
ws.send(JSON.stringify({ action: 'Authorize', idTagInfo }));
};
// --- StartTransaction ---
const startTransaction = async (ws, idTag) => {
const transactionId = generateTransactionId();
await Transaction.insert({
transactionId,
chargerId: ws.chargerId,
startTime: new Date(),
energyDelivered: 0
});
ws.send(JSON.stringify({ action: 'StartTransaction', transactionId, idTagInfo: { status: 'Accepted' } }));
};
// --- StopTransaction ---
const stopTransaction = async (ws, transactionId) => {
const transaction = await Transaction.findOne({ transactionId });
if (!transaction) return;
await Transaction.update({ transactionId }, { $set: { endTime: new Date() } });
ws.send(JSON.stringify({ action: 'StopTransaction', transactionId, idTagInfo: { status: 'Accepted' } }));
};
// --- MeterValues ---
const meterValues = async (ws, transactionId, values) => {
await Transaction.update({ transactionId }, { $set: { energyDelivered: values } });
ws.send(JSON.stringify({ action: 'MeterValues', transactionId, status: 'Accepted' }));
};
// --- Start HTTPS + WSS Server ---
const FIXED_HOST = "192.168.68.114";
httpsServer.listen(3000, FIXED_HOST, () => {
logToFile(`Servidor WSS ativo em https://${FIXED_HOST}:3000`);
});
Deixe um comentário