OCPP Rails

OCPP OCTT CS coverage Status License

A Ruby on Rails engine for building OCPP 1.6 Central System (CSMS) backends for Electric Vehicle (EV) charging networks. This is a backend-only gem — you build your own UI while OCPP Rails handles the WebSocket transport, data models, and the OCPP message layer.

⚠️ Early alpha — read the compliance status before you rely on this. The Core charging-session flow (boot, authorize, start/stop transaction, meter values, status), remote start/stop, and connector unlock are implemented and covered by real handler/job-driven tests. Most other Central-System→Charge-Point operations and every optional feature profile (Reservation, Smart Charging, Firmware, Diagnostics, Local Auth List, Security certificates) are not implemented yet. See OCPP 1.6 Compliance Status for the exact, per-test-case picture — and please consider contributing a slice.

✨ Features

OCPP Protocol Layer (What This Gem Provides)

  • 📡 WebSocket Communication - ActionCable channel handles bidirectional OCPP messages
  • 🔌 Protocol Handlers - BootNotification, Authorize, Heartbeat, StartTransaction, StopTransaction, MeterValues, StatusNotification
  • 🗄️ Data Models - ChargePoint, ChargingSession, MeterValue, Message (audit log)
  • 🚀 Remote Control Jobs - RemoteStartTransaction, RemoteStopTransaction, UnlockConnector, Reset, ClearCache, GetConfiguration, ChangeConfiguration, ChangeAvailability
  • 📊 Real-time Broadcasts - ActionCable broadcasts for status, sessions, and meter values
  • 💾 SQLite Compatible - Works with async adapter, no Redis required for development

What You Build (Your Application)

  • 🎨 User Interface - Build your own dashboard, charts, and controls
  • 🔐 Authentication - Implement your own user authentication
  • 📱 API Endpoints - Create REST/GraphQL APIs as needed
  • 💼 Business Logic - Billing, reservations, user management, etc.
  • 🎯 Custom Authorization - Override handlers for RFID validation logic

OCPP Compliance

  • Core session flow — inbound BootNotification, Authorize, Heartbeat, Start/StopTransaction, MeterValues, StatusNotification
  • Remote Control — RemoteStartTransaction, RemoteStopTransaction, UnlockConnector (delivery + end-to-end flow, tested)
  • Message Audit — every inbound/outbound frame logged for debugging and compliance
  • Multi-connector — one active session per connector, enforced at the DB level
  • 🚧 Everything else — see the honest, per-test-case OCPP 1.6 Compliance Status below

📋 OCPP 1.6 Compliance Status

Compliance here is measured against the Open Charge Alliance OCPP Compliance Testing Tool (OCTT) test case document (2025-02), Section 3 — the cases that apply when the System Under Test is the Central System. There are 76 such cases. This is the role ocpp-rails fills, so it is the right yardstick.

Where we are today:

Cases What it means
Implemented + tested 32 Works and guarded by a real handler/job-driven test
🟡 Implemented — needs test 0 Behavior works, but only simulation-style tests exist; needs a real regression test
🔴 Not implemented 42 The message/operation does not exist in the engine yet
Out of scope 2 TLS handshake (TC_086/087) — belongs in your infra, not app code

So 32 of 76 OCTT Central-System cases (42%) are backed by working code, and every one now has real automated (handler/job-driven) coverage — the 🟡 bucket is empty. The entire Core outbound command set is done; the rest — Local Auth List, Firmware, Diagnostics, Reservation, Remote Trigger, Smart Charging, DataTransfer and the Security profiles 2/3 — is not built yet. Treat this gem as a solid, well-tested Core-profile foundation to build on, not a certified CSMS.

By feature area (each links to the detailed Given/When/Then specs):

Area Status Notes
Boot / Charging Sessions / Cache ✅ implemented + tested Core flow and ClearCache tested end-to-end
Remote Start / Stop ✅ implemented + tested delivery and end-to-end session flow tested
Reset / Unlock / Configuration ✅ implemented + tested Reset, UnlockConnector, Get/ChangeConfiguration all tested (+ ChangeAvailability)
Authorize non-happy paths ✅ tested Invalid / Expired / Blocked on Authorize.req tested
Offline / power-loss ✅ tested replay + power-loss recovery sequences tested
Local Authorization List 🔴 not implemented no SendLocalList / GetLocalListVersion
Firmware Management 🔴 not implemented no UpdateFirmware / FirmwareStatusNotification
Diagnostics 🔴 not implemented no GetDiagnostics / DiagnosticsStatusNotification
Reservation 🔴 not implemented no ReserveNow / CancelReservation, no model
Remote Trigger 🔴 not implemented no TriggerMessage
Smart Charging 🔴 not implemented no SetChargingProfile / ClearChargingProfile / GetCompositeSchedule
DataTransfer 🔴 not implemented inbound DataTransfer currently gets a NotSupported CALLERROR
Security (profiles 1–3) 🟡 Basic auth only HTTP Basic Auth works + tested; certificates/secure firmware/TLS not

👉 Full per-test-case breakdown with Given/When/Then specs: docs/octt-test-plan.md. It doubles as a ready-made contribution backlog — every 🔴 and 🟡 is a self-contained PR.

🚀 Quick Start

Installation

Add this line to your application's Gemfile:

gem "ocpp-rails"

Execute the bundle command:

bundle install

Setup

Run the installation generator:

rails generate ocpp:rails:install

This will:

  • ✅ Create database migrations for charge points, sessions, and meter values
  • ✅ Mount the engine at /ocpp (ActionCable WebSocket endpoint)
  • ✅ Generate an initializer at config/initializers/ocpp_rails.rb
  • ✅ Configure ActionCable for SQLite compatibility
  • ✅ Display setup instructions

Run the migrations:

rails db:migrate

Configuration

Configure OCPP settings in config/initializers/ocpp_rails.rb:

Ocpp::Rails.setup do |config|
  config.ocpp_version = "1.6"
  config.supported_versions = ["1.6"]
  config.heartbeat_interval = 300  # 5 minutes
  config.connection_timeout = 30   # 30 seconds
end

Charge points connect to:

ws://your-server:3000/ocpp/cable

Stations authenticate with HTTP Basic Auth on the WebSocket upgrade (OCPP-J Security Profile 1, enabled by default). Provision a per-station credential first:

charge_point.update!(auth_password: SecureRandom.base58(32))

For detailed setup instructions, see the Getting Started Guide and the Security Guide.

💡 Usage Examples

Monitor Charge Point Status

# Query charge points
connected_cps = Ocpp::Rails::ChargePoint.connected
available_cps = Ocpp::Rails::ChargePoint.available
charging_cps = Ocpp::Rails::ChargePoint.charging

# Check specific charge point
cp = Ocpp::Rails::ChargePoint.find_by(identifier: "CP001")
cp.connected?       # => true/false
cp.status           # => "Available", "Charging", etc.
cp.last_heartbeat_at

Monitor Active Sessions

# Get active sessions
active_sessions = Ocpp::Rails::ChargingSession.active

# Get session details
session = cp.current_session
session.connector_id
session.energy_consumed  # kWh
session.duration_seconds

Monitor Meter Values

# Get latest readings
latest_energy = cp.meter_values.energy.recent.first
latest_power = cp.meter_values.power.recent.first

# Get readings for a session
session.meter_values.order(:timestamp)

Real-Time Updates via ActionCable

Subscribe to real-time broadcasts in your UI:

# app/channels/meter_values_channel.rb
class MeterValuesChannel < ApplicationCable::Channel
  def subscribed
    charge_point = Ocpp::Rails::ChargePoint.find(params[:charge_point_id])
    stream_from "charge_point_#{charge_point.id}_meter_values"
  end
end
// Subscribe in JavaScript
subscribeToMeterValues(chargePointId, {
  onPower: (data) => {
    updatePowerGauge(data.value);
  },
  onEnergy: (data) => {
    updateEnergyCounter(data.value);
  }
});

Remote Control

# Start a charging session
charge_point = Ocpp::Rails::ChargePoint.find_by(identifier: "CP001")

Ocpp::Rails::RemoteStartTransactionJob.perform_later(
  charge_point.id,
  1,              # connector_id
  "RFID12345"     # id_tag
)

# Stop a charging session
session = charge_point.current_session
Ocpp::Rails::RemoteStopTransactionJob.perform_later(
  charge_point.id,
  session.transaction_id
)

For complete implementation examples, see the Remote Charging Guide.

📚 Documentation

Getting Started

Implementation Guides

Development

Reference

🏗️ Architecture

Models

  • Ocpp::Rails::ChargePoint - Represents a physical charging station

    • Tracks connection status, firmware version, vendor info
    • Has many charging sessions and meter values
    • Provides status scopes (connected, available, charging)
  • Ocpp::Rails::ChargingSession - Represents a charging transaction

    • Manages session lifecycle (active/completed)
    • Calculates energy consumption and duration automatically
    • Links to meter values for detailed tracking
  • Ocpp::Rails::MeterValue - Stores individual meter readings

    • Supports 22+ OCPP measurands (Energy, Power, Current, Voltage, SoC, Temperature, etc.)
    • Can be transaction-specific or standalone
    • Includes timestamp, phase, context, and location
  • Ocpp::Rails::Message - Logs all OCPP communications

    • Stores direction (inbound/outbound)
    • Includes full message payload as JSONB
    • Enables debugging and compliance auditing

Communication Flow

┌─────────────────┐                    ┌─────────────────┐
│  Your Rails App │                    │  Charge Point   │
│  (Central System)│                   │     (OCPP 1.6)  │
└────────┬────────┘                    └────────┬────────┘
         │                                      │
         │  1. RemoteStartTransaction           │
         ├─────────────────────────────────────>│
         │                                      │
         │  2. StartTransaction                 │
         │<─────────────────────────────────────┤
         │                                      │
         │  3. MeterValues (periodic)           │
         │<─────────────────────────────────────┤
         │                                      │
         │  4. RemoteStopTransaction            │
         ├─────────────────────────────────────>│
         │                                      │
         │  5. StopTransaction                  │
         │<─────────────────────────────────────┤

🧪 Testing

# Run all tests
rails test

# Run the handler/job-driven unit tests (the ones that exercise real code)
rails test test/ocpp/

# Run a specific test file
rails test test/ocpp/outbound_delivery_test.rb

A note on what the tests prove. The suite has two kinds of tests:

  • Handler/job-driven tests push real frames through MessageHandler/Actions::*Handler/jobs and assert on actual behavior — e.g. test/ocpp/message_handler_test.rb, outbound_delivery_test.rb, station_authentication_test.rb, start_transaction_authorization_test.rb, authorize_handler_test.rb, boot_notification_handler_test.rb, status_notification_handler_test.rb, unlock_connector_job_test.rb, and several under test/ocpp/integration/ (remote_start_flow_test.rb, remote_stop_flow_test.rb, offline_transaction_test.rb, power_failure_recovery_test.rb). These are what the compliance status above counts.
  • Simulation-style tests — the older files under test/ocpp/integration/ (e.g. *_transaction_test.rb, remote_charging_session_workflow_test.rb, authorize_test.rb, boot_notification_test.rb) build request/response hashes and model rows by hand without invoking production code. They document expected message shapes but do not prove wire-protocol compliance, so they are not counted toward OCTT coverage.

When you implement a 🔴/🟡 case from the test plan, please add a handler/job-driven test for it. See the Testing Guide for details.

🗺️ Roadmap

Implemented today

  • ✅ Core inbound session flow (Boot, Authorize, Heartbeat, Start/StopTransaction, MeterValues, StatusNotification)
  • ✅ Remote start/stop transactions (delivery + end-to-end flow) and UnlockConnector — releasable even during an active session
  • ✅ Device management: Reset (Hard/Soft), ClearCache, ChangeAvailability, and Get/ChangeConfiguration
  • ✅ Real-time meter value / status / session broadcasts
  • ✅ Session management, energy/duration tracking, meter-anomaly + timestamp-provenance checks
  • ✅ OCPP-J Security Profile 1 (HTTP Basic Auth) + per-station rate limiting
  • ✅ Complete inbound/outbound message logging
  • ✅ Handler/job-driven OCTT regression suite for all 32 implemented Core-profile cases

Not implemented yet (contributions very welcome — see the test plan)

  • 🔴 Local Authorization List (SendLocalList, GetLocalListVersion)
  • 🔴 Firmware updates + FirmwareStatusNotification
  • 🔴 Diagnostics upload + DiagnosticsStatusNotification
  • 🔴 Reservation system (ReserveNow, CancelReservation)
  • 🔴 Remote Trigger (TriggerMessage)
  • 🔴 Smart charging profile management (Set/Clear/GetCompositeSchedule)
  • 🔴 Inbound DataTransfer handling
  • 🔴 Security profiles 2/3 (certificate management, secure firmware, security events)
  • 📝 OCPP 2.0.1 support

🤝 Contributing

This project needs you. It's an honest, well-tested Core-profile foundation with a long list of OCPP features still to build — and that list is already written up as a ready-to-pick-up backlog.

Where to start

docs/octt-test-plan.md maps all 76 OCTT Central-System test cases to their status. Every 🔴 (not implemented) and 🟡 (needs a real test) entry comes with a Given/When/Then spec and a suggested test file — so each one is a self-contained, well-scoped PR. Good first issues:

  • 🟡 Add a real test for something that already works — pick a 🟡 case (e.g. the Authorize non-happy paths, or the boot flow) and write a handler-driven test against the spec. No new production code needed.
  • 🔴 Implement one operation — the outbound Core command set is complete; the next self-contained targets are Remote Trigger (TriggerMessage) and the Local Auth List commands (GetLocalListVersion, SendLocalList), following the same job pattern as RemoteStartTransactionJob / ResetJob.
  • 🔴 Build out a feature profile — Reservation, Smart Charging, Firmware, etc. are larger efforts; open an issue first so we can sketch the model/API together.

Ground rules

  • Match the OCTT spec in the test plan — cite the TC_xxx_CSMS id in your PR.
  • Add a handler/job-driven test (not a simulation-style hash test) for any behavior you implement, and update the case's status in docs/octt-test-plan.md and the summary in this README.
  • For major changes, open an issue first to discuss the design.

Development Setup

# Clone the repository
git clone https://github.com/trahfo/ocpp-rails.git
cd ocpp-rails

# Install dependencies
bundle install

# Setup test database
cd test/dummy
rails db:migrate RAILS_ENV=test
cd ../..

# Run tests
rails test

Running the Test Suite

# Run all tests with coverage
rails test

# Run with verbose output
rails test -v

# Run specific test file
rails test test/ocpp/integration/remote_charging_session_workflow_test.rb

📄 License

This gem is available as open source under the terms of the MIT License.

🙏 Acknowledgments

📞 Support


Status: 🔧 Alpha - Under Active Development
Version: 0.2.0
OCPP: 1.6 Edition 2
Rails: 8.0+
Ruby: 3.3+