CitrineOS Core

repository·main·Indexed 42 days ago

https://github.com/citrineos/citrineos-core

An open-source, modular server runtime for managing Electric Vehicle (EV) charging infrastructure using OCPP protocols. Includes @citrineos/mock-msp, a high-fidelity mock e-Mobility Service Provider (eMSP) for testing CPO-side OCPI 2.2.1 implementations, featuring a FaultEngine for traffic perturbation and a Control API for state inspection and scenario simulation.

Tokens
110.8K
Snippets
310
Records
470
Agent score
52%

What's inside CitrineOS

  1. Overview of CitrineOS architecture and features

    main

    CitrineOS is a modular server runtime for managing Electric Vehicle (EV) charging infrastructure. It is built with TypeScript and runs on NodeJS using fastify and ws.

    Key Features

    • OCPP Support: Dynamic schema validation for OCPP 1.6 and 2.0.1 using AJV.
    • API Surface:
      • Generates OpenAPIv3 specifications.
      • Uses @AsRequestHandler and @AsResponseHandler decorators to declare message handlers.
      • Provides an Api module with AbstractMessageEndpoint (for /ocpp/<version>/... routes to stations) and AbstractEndpoint (for /commands admin operations).
    • Infrastructure:
      • Message Broker: Supports RabbitMQ for inter-module communication.
      • Caching: Supports In Memory and Redis.
      • Persistence: Uses PostgreSQL with the PostGIS extension.
      • Storage: Supports Amazon S3, Google Cloud Storage (GCS), and MinIO (for local development). Note that local development does not support GCS-compatible backends.
    • Operator UI: A web interface built with Next.js and Refine for managing locations, stations, transactions, and authorizations.
  2. Overview of CitrineOS Operator UI

    main

    The CitrineOS Operator UI is a React-based web interface built with Refine for managing Electric Vehicle (EV) charging infrastructure. It provides operators with visibility into charging stations, connectors, sessions, and transactions, as well as operational controls.

    Important: The Operator UI does not run standalone. It requires a running instance of CitrineOS Core and Hasura GraphQL Engine to function.

    Data Flow Architecture

    • Hasura GraphQL APIs: Used for data access and management (connected to PostgreSQL).
    • CitrineOS Core Message APIs: Used for sending commands to charging stations via REST.
    • CitrineOS Core Data APIs: Used for managing entities.
  3. Use the CitrineOS Operator UI

    main

    The Operator UI is a management interface that provides the following capabilities:

    • Station Management: View and manage charging stations and connectors.
    • Monitoring: Monitor transactions and sessions.
    • Command Execution: Trigger charging station commands via CitrineOS Core.
    • Data Management: Query and manage data through Hasura-backed GraphQL APIs.
  4. Overview of @citrineos/mock-msp

    main

    The @citrineos/mock-msp package is a mock e-Mobility Service Provider (eMSP) designed to test CitrineOS's CPO-side OCPI implementation. It impersonates the seeded partner US/TST ("TestMobilitySolutions") and communicates via OCPI 2.2.1.

    Key characteristics:

    • Port: Listens on port 8083.
    • Schema Fidelity: Reuses @citrineos/ocpi-base Zod schemas to ensure the mock and CitrineOS never disagree on data contracts.
    • Internal Components:
      • Recorder: Creates an immutable, queryable trace of all inbound and outbound Exchange events.
      • Actor: Handles the credentials handshake, sends Commands to Citrine, hosts response_url callbacks, and pulls SENDER endpoints.
      • Adversary: A FaultEngine used to inject traffic perturbations (delays, aborts, malformed bodies, etc.) when a scenario is armed.
    • Control API: A /_mock/* surface used to inspect traces, drive the Actor, arm faults, and run assertions.

    Note: This is a private/dev tool. The package is marked private: true and is not intended for production release.

  5. Configure auto-commissioning via 'Allow Unknown Charging Stations'

    main

    The System Configuration includes a boolean property allowUnknownChargingStations. When enabled, CitrineOS permits charging stations that have not been formally commissioned to connect to the websocket server.

    Behavior:

    • Upon connection, a ChargingStations row is automatically created.
    • evses and connectors are created in response to StatusNotifications.

    ⚠️ WARNING: DO NOT USE IN PRODUCTION.

    • This feature is intended for development and testing only.
    • By default, it is only enabled on the websocket server at port 8081, which has no security.
    • OCPP 1.6 Limitation: Multi-EVSE stations will not commission properly because 1.6 lacks a native 'evse' concept. This can lead to inconsistent states where CitrineOS incorrectly marks older transactions as inactive.
  6. Understand the difference between reset -d '{}' and reset -d '{"keepRegistration":true}'

    main

    The reset command behaves differently depending on the payload provided:

    • reset -d '{}': Rebuilds registration from the configuration. This restores the registered status and both tokens. Use this as your "panic button" to fix broken states.
    • reset -d '{"keepRegistration":true}': Preserves the current state. If the current state is broken (e.g., unregistered), this will not rescue you.
  7. How the OCPI Server connects to CitrineOS Core

    main

    The OCPI Server acts as a bridge between roaming partners (eMSP/CPO) and the CitrineOS Core. It does not own its own database or broker; instead, it connects to the services provided by the Core stack via the shared citrineos network (when running in Docker):

    • ocpp-db: PostgreSQL for shared persistence.
    • amqp-broker: RabbitMQ for messaging via the shared citrineos exchange.
    • citrine: The OCPP server's Message APIs, used to dispatch commands like remote start/stop or unlocking connectors.
    • graphql-engine: Hasura GraphQL Engine for data access.
  8. Understand the CitrineOS HTTP API Surfaces

    main

    CitrineOS exposes three distinct REST API surfaces, each identified by a specific URL prefix. Depending on your goal (sending OCPP messages, executing system commands, or managing the router), you will target one of these three:

    1. OCPP Surface (/ocpp/<version>/<module>/<action>): Used to send OCPP messages to charging stations. The <version> segment specifies the protocol (e.g., 1.6, 2.0.1), and <module> is determined by the configured endpointPrefix.
    2. Commands Surface (/commands/<command>): Used for CitrineOS-native operations that are not direct OCPP messages (e.g., POST /commands/setStationPassword).
    3. Router Administration Surface (/ocpprouter/<resource>): Used for managing the router, such as websocket configuration, tenant mappings, and TLS reloading (e.g., DELETE /ocpprouter/connection).
    # Examples of API paths:
    POST /ocpp/1.6/configuration/changeAvailability?identifier=cs001
    POST /commands/setStationPassword
    DELETE /ocpprouter/connection
  9. Analyze OCPI coverage with the Coverage Matrix

    main

    The dashboard provides a coverage matrix to track which OCPI modules and directions have been exercised.

    • Rows: OCPI modules (e.g., locations, cdrs, sessions, tariffs, tokens).
    • Columns: Inbound (C→M) and Outbound (M→C) directions.
    • Green: Module exercised and the last interaction was valid.
    • Red: Module exercised and the last interaction failed schema validation.
    • Grey: Module has not been exercised.
    • n/a locally: Indicates interactions that cannot be simulated via the dashboard (e.g., real-time token authorize or async command result which require physical hardware/stations).
  10. Understand Mock eMSP dashboard status indicators

    main

    The dashboard provides real-time health and state information through several key indicators:

    Registration Status

    Located in the header, this indicates the OCPI handshake state:

    • Green registered: Handshake complete; tokens and endpoints are exchanged.
    • Amber unregistered: Handshake not yet performed.
    • Red server down: Connection to CitrineOS is lost.

    Scenario Badge

    Displays the currently loaded scenario file (e.g., preregistered). A scenario is a JSON file defining the starting state, fault configurations, and test assertions.

    Summary Cards

    • EXCHANGES: Count of complete HTTP conversations recorded.
    • FINDINGS: Count of failed contract inspections. A single finding (one failed response) can contain multiple issues (individual broken fields).
    • FAULTS ARMED: Number of active "misbehave on purpose" rules. Amber indicates faults are currently armed; check this before clicking to avoid unintended errors.
    • AUTHORIZE: The standing answer to CitrineOS's authorization requests (e.g., ALLOWED or BLOCKED).
  11. How internationalization (i18n) works in Operator UI

    main

    The UI uses next-intl integrated with Refine's i18nProvider.

    • Supported Languages: English (en, default) and Brazilian Portuguese (pt-BR).
    • Storage: The selected language is stored in the NEXT_LOCALE cookie.
    • Translation Files: Located in public/locales/<locale>/. Files are organized by domain (e.g., common.json, chargingStations.json).
    • Structure: Files use a PascalCase root key with camelCase sub-keys (e.g., ChargingStations.noDataFound).

    Using translations in components

    Use the useTranslate hook from @refinedev/core to resolve strings.

    import { useTranslate } from '@refinedev/core';
    
    const translate = useTranslate();
    // Simple key
    translate('Common.save');
    // Key with ICU parameters
    translate('Locations.noDataFound', { id });
    import { useTranslate } from '@refinedev/core';
    
    const translate = useTranslate();
    translate('Common.save');
    translate('Locations.noDataFound', { id });
  12. Configure Scenarios in Mock MSP

    main

    A scenario is a configuration file (located in apps/mock-msp/scenarios/*.json) that defines the adversary configuration, registration state, and assertion fixtures.

    Scenarios can be loaded at boot using the MOCK_MSP_SCENARIO environment variable or hot-loaded at runtime via POST /_mock/scenario.

    Scenario Schema

    • name: String identifier.
    • registration: Either preregistered (uses seed tokens) or unregistered (requires handshake).
    • identity: Optional Partial<OcpiIdentity> override.
    • authorize: Defines the tokens/{uid}/authorize policy. Supports a default (e.g., ALLOWED, BLOCKED) and byUid overrides.
    • strictInbound: If true, schema-invalid inbound bodies return a 2001 error; otherwise, they are recorded and accepted.
    • faults: An array of FaultRule objects.
    • expect: An array of assertion oracles used with POST /_mock/scenarios/:id/evaluate.
    {
      "name": "authorize-blocked",
      "registration": "preregistered",
      "identity": { /* optional override */ },
      "authorize": {
        "default": "ALLOWED",
        "byUid": { "04E7F5A2B37C80": "BLOCKED" }
      },
      "strictInbound": false,
      "faults": [ /* FaultRule[] */ ],
      "expect": [
        { "on": "tokens.authorize", "assert": "response.body.data.allowed == BLOCKED" }
      ]
    }