Um servidor NodeJS para Carregadores Elétricos usando OCPP1.6 com banco de dados NeDB

🔹 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:

  1. Linux (Debian/Ubuntu)

    sudo apt update
    sudo apt install -y nodejs npm
  2. Windows

    • Baixar o instalador no site oficial: https://nodejs.org

    • Instalar e usar via PowerShell ou CMD.

  3. macOS

    brew install node
  4. Instalação recomendada (NVM – Node Version Manager)

    curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash
    source ~/.bashrc
    nvm install --lts

Verificar instalação:

node -v
npm -v

🔹 Configurando a Aplicação

O arquivo do servidorOCPP.js  está listado abaixo. Agora vamos preparar tudo para rodá-lo.

1. Criar pasta do projeto

mkdir servidor-ocpp
cd servidor-ocpp

2. Criar package.json

npm init -y

3. Instalar dependências necessárias

npm install ws nedb-promises

O fs e https 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:

openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out cert.csr
openssl x509 -req -days 365 -in cert.csr -signkey key.pem -out cert.pem

Isso gera:

  • key.pem → chave privada

  • cert.pem → certificado público

🔹 Executando o Servidor

Para iniciar:

node servidorOCPP.js

Se tudo estiver certo, você verá no log algo como:

Servidor WSS ativo em https://192.168.68.114:3000

🔹 Estrutura final do projeto

servidor-ocpp/
                              │── servidorOCPP.js
                              │── key.pem
             │── cert.pem
             │── charger.db
                             │── transaction.db
                             │── server.log
                             │── package.json
                             │── node_modules/

✅ 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

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