### Run Fresh Install and Setup Source: https://github.com/citrineos/citrineos-core/blob/main/README.md A convenience command that first runs `fresh` to clean the workspace and then executes `pnpm install` to set up dependencies. ```bash pnpm run fi ``` -------------------------------- ### Start Operator UI Development Server Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/README.MD After installing dependencies, navigate to the operator-ui directory and run this command to start the development server with hot reload. This utilizes Next.js and Refine. ```bash # then, from apps/operator-ui pnpm run dev ``` -------------------------------- ### FtpServer Usage Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Example of initializing and starting an FTP server. Ensure all required options like host, port, credentials, and data directory are provided. ```typescript const ftpServer = new FtpServer({ host: 'ftp.example.com', port: 21, username: 'ftpuser', password: 'ftppass', dataDir: '/var/ftp/data', }); await ftpServer.start(); // Stop when needed await ftpServer.stop(); ``` -------------------------------- ### Install Workspace Dependencies Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/README.MD Run this command from the repository root to install all necessary dependencies for the pnpm workspace before starting the development server. ```bash # from the repository root pnpm install ``` -------------------------------- ### RedisCache Usage Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Demonstrates initializing and using the RedisCache with connection details. Shows setting and getting data. ```typescript import { RedisCache } from '@citrineos/core'; const cache = new RedisCache({ host: 'redis.example.com', port: 6379, }); await cache.set('stationIds', ['CS-001', 'CS-002']); const stations = await cache.get('stationIds'); ``` -------------------------------- ### Start UI Server Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/tests/e2e/README.md Run this command from the `apps/operator-ui` directory to start the UI server for testing. ```bash pnpm run dev ``` -------------------------------- ### RepositoryStore Usage Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/04-data-access.md Shows how to obtain an instance of RepositoryStore and access individual repositories to perform operations like finding all stations or paginating transactions. Use the static getInstance() method to get the store. ```typescript const store = RepositoryStore.getInstance(); const stations = await store.chargingStation().findAll(); const transactions = await store.transaction().findByPagination(); ``` -------------------------------- ### Install and Build Workspace Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/README.MD Install dependencies and build the entire workspace from the repository root. ```bash pnpm install pnpm run build ``` -------------------------------- ### Build and Start Operator UI in Production Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/README.MD Builds the application for production and then starts it. The production URL is http://localhost:3000. ```bash pnpm run build pnpm run start ``` -------------------------------- ### Run Full Stack with Docker Compose (Published Images) Source: https://github.com/citrineos/citrineos-core/blob/main/README.md Use this command to start the entire CitrineOS stack using pre-built Docker images from Docker Hub. This is the quickest way to get the environment running without a build step. ```shell docker compose up -d ``` -------------------------------- ### Start EVerest Simulator Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/README.MD Command to start the EVerest simulator from the 'Server' directory within 'citrineos-core'. ```bash cd citrineos-core/apps/Server pnpm run start-everest ``` -------------------------------- ### Pagination Request Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Example of a GET request to a data endpoint with limit and offset parameters for pagination. ```http GET /data/transaction?limit=20&offset=40 ``` -------------------------------- ### Start CitrineOS Server Source: https://github.com/citrineos/citrineos-core/blob/main/README.md Starts the main CitrineOS server. This command delegates to the `@citrineos/server` package. ```bash pnpm run start ``` -------------------------------- ### Install Hasura CLI Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/README.md Use this command to download and install the Hasura CLI if it is not already installed on your system. ```bash curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash ``` -------------------------------- ### Start EVerest with OCPP 2.0.1 Support Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/everest/README.md Execute this command within the EVerest manager container to start EVerest with OCPP 2.0.1 support after the device model has been copied. ```bash docker exec everest-ac-demo-manager-1 sh /ext/source/build/run-scripts/run-sil-ocpp201.sh ``` -------------------------------- ### List Tenants Response Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md An example of the JSON response when listing all tenants. Each tenant object includes basic information like ID, name, and domain. ```json [ { "id": 1, "name": "Tenant A", "code": "tenant-a", "domain": "tenanta.example.com", "isActive": true, "created": "2024-01-01T00:00:00Z" } ] ``` -------------------------------- ### Start EVerest for OCPP 1.6 Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/everest/README.md Use this command to start EVerest with OCPP 1.6 support. Ensure you are in the 'apps/Server' directory. ```bash pnpm run start-everest-16 ``` -------------------------------- ### Start EVerest for OCPP 2.x Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/everest/README.md Use this command to start EVerest with OCPP 2.x support. It defaults to OCPP_VERSION=2.1. Ensure you are in the 'apps/Server' directory. ```bash pnpm run start-everest ``` -------------------------------- ### GET /data/systemconfig Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Get the current system configuration. ```APIDOC ## GET /data/systemconfig ### Description Get current system configuration. ### Method GET ### Endpoint /data/systemconfig ### Response #### Success Response (200) - **SystemConfig** (object) - The full system configuration object. ``` -------------------------------- ### Module Testing Setup Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Illustrates how to set up and test a module using a testing framework. It covers initialization with configuration and asserting the handling of custom actions. ```typescript describe('MyModule', () => { let module: MyModule; let config: SystemConfig; beforeEach(async () => { config = defineConfig({ /* test config */ }); module = new MyModule(); await module.initialize(config); }); it('should handle MyCustomAction', async () => { const request: MyCustomActionRequest = { /* test data */ }; const response = await module.handleMyCustomAction(request); expect(response.status).toBe('Accepted'); }); }); ``` -------------------------------- ### Filtering Request Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Example of a GET request to a data endpoint with stationId and status query parameters for filtering. ```http GET /data/transaction?stationId=CS-001&status=Active ``` -------------------------------- ### Install Workspace Dependencies with pnpm Source: https://github.com/citrineos/citrineos-core/blob/main/README.md After cloning the repository, run this command from the root directory to install all necessary workspace dependencies using pnpm. ```shell pnpm install ``` -------------------------------- ### Development Commands Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Essential pnpm commands for setting up the development environment, including installation, building, linting, and testing. ```bash # Development pnpm install pnpm run build pnpm run lint pnpm run lint-fix pnpm run test ``` -------------------------------- ### Example Repository Usage Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/04-data-access.md Demonstrates how to use a concrete repository implementation like SequelizeChargingStationRepository for various data operations. Ensure the repository is imported before use. ```typescript import { SequelizeChargingStationRepository } from '@citrineos/core'; const repository = new SequelizeChargingStationRepository(); // Create const station = await repository.create({ stationId: 'CS-001', tenantId: 1, }); // Find by ID const found = await repository.findById(station.id); // Find with filtering const pending = await repository.findOne({ stationId: 'CS-001', tenantId: 1, }); // Find all with pagination const { rows, count } = await repository.findByPagination( { tenantId: 1 }, { limit: 10, offset: 0 } ); // Update const updated = await repository.update(station.id, { lastHeartbeat: new Date(), }); // Delete await repository.delete(station.id); ``` -------------------------------- ### Run Server Directly with pnpm Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/README.md Start the server directly using pnpm from the repository root or the server directory. This command builds the workspace, runs database migrations, and starts the Node.js process. ```shell pnpm run start ``` ```shell cd apps/Server pnpm run start ``` -------------------------------- ### Install Certificate Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Installs a certificate on the charging station. Supports different types of certificates for enhanced security. ```APIDOC ## POST /ocpp/2.0.1/certificates/installCertificate ### Description Install certificate on charging station. ### Method POST ### Endpoint /ocpp/2.0.1/certificates/installCertificate ### Request Body - **certificateType** (string) - Required - Type of certificate to install. - **certificate** (string) - Required - Certificate in PEM format. ``` -------------------------------- ### Response Format Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md This is an example of the standard response format for message endpoints. ```json [ { "identifier": "CS-001", "status": "Accepted", "statusDescription": "Message delivered", "timestamp": "2024-01-15T10:30:00Z", "tenantId": 1 } ] ``` -------------------------------- ### List Transactions Response Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Example JSON response for listing all transactions. Includes transaction ID, station ID, start/stop times, meter readings, total energy, and tenant ID. ```json [ { "id": 1, "transactionId": "TX-001", "stationId": "CS-001", "startTime": "2024-01-15T10:00:00Z", "stopTime": "2024-01-15T11:30:00Z", "meterStart": 1000, "meterStop": 2150, "totalEnergy": 1150, "tenantId": 1 } ] ``` -------------------------------- ### Running CitrineOS Commands Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Commands to start the CitrineOS server, either using a local build or Docker. ```bash # Running pnpm run start # Start server pnpm run start-docker # Docker with local build ``` -------------------------------- ### Install Certificate Response Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md The response indicates whether the certificate installation was accepted, rejected, or failed, with optional details about the status. ```typescript interface InstallCertificateResponse { status: 'Accepted' | 'Rejected' | 'Failed'; statusInfo?: { reasonCode: string; additionalInfo?: string; }; } ``` -------------------------------- ### LocalStorage Usage Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Example of initializing and using the LocalStorage class. Specify the base directory for file operations. ```typescript const storage = new LocalStorage('/var/citrineos/files'); const filePath = await storage.uploadFile( 'certificates', 'cert.pem', certContent, ); const content = await storage.downloadFile('certificates', 'cert.pem'); ``` -------------------------------- ### Environment Variable Examples for CitrineOS Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/03-configuration.md Illustrates how to set environment variables for basic settings, module configurations, cache settings, message broker connection, and authentication providers. Also shows how to use a custom prefix. ```bash # Basic settings export CITRINEOS_ENV=production export CITRINEOS_CENTRALSYSTEM_HOST=0.0.0.0 export CITRINEOS_CENTRALSYSTEM_PORT=8081 # Module configuration export CITRINEOS_MODULES_CONFIGURATION_HEARTBEATINTERVAL=60 export CITRINEOS_MODULES_TRANSACTIONS_COSTUPDATE DINTERVAL=30 # Cache configuration export CITRINEOS_UTIL_CACHE_REDIS_HOST=redis.example.com export CITRINEOS_UTIL_CACHE_REDIS_PORT=6379 # Message broker export CITRINEOS_UTIL_MESSAGEBROKER_AMQP_URL=amqp://rabbitmq:5672 # Auth provider export CITRINEOS_UTIL_AUTHPROVIDER_OIDC_JWKSURI=https://auth.example.com/.well-known/jwks.json export CITRINEOS_UTIL_AUTHPROVIDER_OIDC_ISSUER=https://auth.example.com export CITRINEOS_UTIL_AUTHPROVIDER_OIDC_AUDIENCE=my-api # Custom prefix node app.js --env-prefix=myapp_ export MYAPP_ENV=production ``` -------------------------------- ### Install Certificate Request Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Use this endpoint to install a certificate on the charging station. The request requires the certificate type and the certificate in PEM format. ```typescript interface InstallCertificateRequest { certificateType: 'MORootCertificate' | 'ManufacturerRootCertificate' | 'CSMSRootCertificate' | 'ManufacturerOperatorRootCertificate'; certificate: string; // PEM format } ``` -------------------------------- ### Sentry Integration Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/errors.md Initializes the Sentry SDK and captures an exception within a try-catch block. Ensure the SENTRY_DSN environment variable is set. ```typescript import * as Sentry from "@sentry/node"; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: config.env, }); try { // operation } catch (error) { Sentry.captureException(error, { tags: { module: 'Transactions', stationId: request.stationId, }, }); } ``` -------------------------------- ### S3Storage Configuration and Usage Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Example of configuring and using the S3Storage class for file operations. Ensure correct credentials and region are provided. ```typescript const storage = new S3Storage({ region: 'us-east-1', credentials: { accessKeyId: 'YOUR_ACCESS_KEY', secretAccessKey: 'YOUR_SECRET_KEY', }, }); // Upload file const url = await storage.uploadFile( 'certificates', 'station-cert.pem', certContent, 'application/x-pem-file', ); // Download file const content = await storage.downloadFile('certificates', 'station-cert.pem'); // List files const files = await storage.listFiles('certificates', 'prefix/'); // Delete file await storage.deleteFile('certificates', 'station-cert.pem'); ``` -------------------------------- ### GcpCloudStorage Configuration Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Example of configuring the GcpCloudStorage class with project ID and service account key file path. ```typescript const storage = new GcpCloudStorage({ projectId: 'my-project', keyFilename: '/path/to/service-account-key.json', }); ``` -------------------------------- ### Register System Configuration Routes Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/02-module-api.md Automatically registers GET and PUT routes for managing module system configuration. The GET route retrieves the current configuration, and the PUT route updates it. ```typescript protected registerSystemConfigRoutes(module: T): void ``` -------------------------------- ### Build All Packages with pnpm Source: https://github.com/citrineos/citrineos-core/blob/main/README.md Once dependencies are installed, execute this command from the root directory to build all packages within the monorepo. ```shell pnpm run build ``` -------------------------------- ### GET /data/tenant Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md List all tenants. ```APIDOC ## GET /data/tenant ### Description List all tenants. ### Method GET ### Endpoint /data/tenant ### Response #### Success Response (200) - **id** (number) - Description of the tenant ID. - **name** (string) - Description of the tenant name. - **code** (string) - Description of the tenant code. - **domain** (string) - Description of the tenant domain. - **isActive** (boolean) - Description of the tenant active status. - **created** (string) - Description of the tenant creation timestamp. ``` -------------------------------- ### Module Class Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Demonstrates how to create a custom module by extending `AbstractModule` and defining a handler for a specific action using the `@AsHandler` decorator. ```APIDOC ## Creating a Module Class ### Description This section shows how to define a new module by extending `AbstractModule` and registering a handler for a specific action. ### Class Definition ```typescript import { AbstractModule, AsHandler, OCPPVersion } from '@citrineos/base'; export class MyModule extends AbstractModule { name = 'MyModule'; @AsHandler([OCPPVersion.OCPP2_0_1], 'MyAction') async handleMyAction(request: any): Promise { this._logger.info('Processing MyAction'); return { status: 'Accepted' }; } } ``` ``` -------------------------------- ### Use ConfigStore Factory Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/03-configuration.md Demonstrates how to get an instance of the ConfigStore and save a new configuration. Ensure a SystemConfig object is available before calling saveConfig. ```typescript const store = ConfigStoreFactory.getInstance(); await store.saveConfig(newConfig); ``` -------------------------------- ### registerSystemConfigRoutes() Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/02-module-api.md Automatically registers GET and PUT routes for retrieving and updating system configuration. ```APIDOC ## Method registerSystemConfigRoutes() ### Description Automatically registers GET and PUT routes for retrieving and updating system configuration. ### Signature ```typescript protected registerSystemConfigRoutes(module: T): void ``` ### Routes Generated - GET `/data/systemconfig` - Returns current module configuration - PUT `/data/systemconfig` - Updates and persists configuration ``` -------------------------------- ### Module Lifecycle Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Outlines the key stages in a module's lifecycle: Initialization, Start, Message Processing, and Stop. ```APIDOC ## Module Lifecycle 1. **Initialization**: Module receives resolved SystemConfig 2. **Start**: WebSocket servers start, message handlers registered 3. **Message Processing**: Incoming OCPP messages routed to handlers 4. **Stop**: Clean shutdown, active connections terminated ``` -------------------------------- ### TOTP Usage Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Demonstrates how to use the TotpUtil class to generate a secret, obtain a QR code for an authenticator app, and verify a user-provided token. ```typescript import { TotpUtil } from '@citrineos/core'; // Generate secret for user const secret = TotpUtil.generateSecret('user@example.com'); // Get QR code for mobile app const qrUrl = TotpUtil.getQrCodeUrl(secret, 'CitrineOS:user@example.com'); // Later: verify token from app const token = '123456'; if (TotpUtil.verifyToken(secret, token)) { console.log('Token valid'); } ``` -------------------------------- ### Get System Configuration Response Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Retrieves the current system configuration. The response is the full `SystemConfig` object in JSON format. ```json Full `SystemConfig` object as JSON. ``` -------------------------------- ### API Class Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Illustrates how to create an API class for a module, defining message and data endpoints using decorators like `@AsMessageEndpoint` and `@AsDataEndpoint`. ```APIDOC ## Creating an API Class ### Description This section demonstrates how to create an API class associated with a module, defining endpoints for handling messages and data operations. ### Class Definition ```typescript import { AbstractModuleApi, AsMessageEndpoint, AsDataEndpoint, HttpMethod } from '@citrineos/base'; export class MyModuleApi extends AbstractModuleApi { @AsMessageEndpoint('MyAction', { /* schema */ }) async myAction( identifiers: string[], request: any, callbackUrl?: string, tenantId?: string | number, ): Promise { return this._module.sender.send(identifiers, { messageTypeId: MessageTypeId.CALL, messageId: uuid(), action: 'MyAction', payload: request, }); } @AsDataEndpoint(OCPP2_Namespace.MyData, HttpMethod.Get) async getMyData(request: FastifyRequest, reply: FastifyReply): Promise { const data = await this.repository.findOne({}); reply.send(data); } } ``` ``` -------------------------------- ### Run a Single E2E Spec Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/tests/e2e/README.md Execute a specific E2E test file, for example, `specs/auth/login.spec.ts`. ```bash npx playwright test specs/auth/login.spec.ts ``` -------------------------------- ### EVDriver Module Settings Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Sets up the EVDriver module, including options for enabling charging profile retrieval upon transaction start. ```typescript modules: { evdriver: { enableGetChargingProfilesOnStartTransaction: true, requests: ['Authorize', 'StartCharging', ...], responses: [...], } } ``` -------------------------------- ### Manually Start EVerest with Docker Compose Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/everest/README.md This command brings up EVerest using a specific Docker Compose file for OCPP 2.0.1. Ensure CitrineOS is running and you are in the EVerest demo repository. ```bash docker compose --project-name everest-ac-demo --file "docker-compose.ocpp201.yml" up -d ``` -------------------------------- ### Pagination Response Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md JSON structure for paginated data responses, including results and pagination metadata. ```json { "data": [ /* results */ ], "pagination": { "limit": 20, "offset": 40, "total": 150, "hasMore": true } } ``` -------------------------------- ### MemoryCache Usage Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Demonstrates how to use the MemoryCache for storing, retrieving, checking, deleting, and flushing data. Includes setting a Time-To-Live (TTL). ```typescript import { MemoryCache } from '@citrineos/core'; const cache = new MemoryCache(); // Store data await cache.set('key', { data: 'value' }, 'namespace', 3600); // 1 hour TTL // Retrieve data const value = await cache.get('key', 'namespace'); // Check existence const exists = await cache.exists('key', 'namespace'); // Delete await cache.delete('key', 'namespace'); // Clear namespace await cache.flush('namespace'); ``` -------------------------------- ### Perform a Fresh Workspace Setup Source: https://github.com/citrineos/citrineos-core/blob/main/README.md Deletes all `node_modules`, `dist`, `tsbuildinfo`, and `pnpm-lock.yaml`, then clears the pnpm cache. Use this when encountering issues after branch switches or dependency changes. ```bash pnpm run fresh ``` -------------------------------- ### Create Custom Module Class Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Example of creating a custom module by extending AbstractModule. Includes initialization logic and handling custom OCPP actions using the @AsHandler decorator. ```typescript import { AbstractModule, AsHandler, OCPPVersion } from '@citrineos/base'; export class MyModule extends AbstractModule { name = 'MyModule'; async initialize(config: SystemConfig): Promise { await super.initialize(config); // Initialize module-specific resources } @AsHandler([OCPPVersion.OCPP2_0_1], 'MyCustomAction') async handleMyCustomAction(req: MyCustomActionRequest): Promise { // Handle incoming OCPP message this._logger.info('Handling MyCustomAction', req); return { status: 'Accepted' }; } } ``` -------------------------------- ### Creating a Custom Module Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/02-module-api.md A guide to creating custom modules, including defining handlers for incoming messages and endpoints for outgoing messages or data retrieval. ```APIDOC ## Creating a Custom Module **Typical Structure:** ```typescript // 1. Create module class export class MyModule extends AbstractModule { @AsHandler([OCPPVersion.OCPP2_0_1], 'MyAction') async handleMyAction(req: MyActionRequest): Promise { // Handle incoming OCPP message } } // 2. Create API class export class MyModuleApi extends AbstractModuleApi { @AsMessageEndpoint('MyAction', { /* schema */ }) async myAction( identifiers: string[], request: MyActionRequest, ): Promise { // Send outbound OCPP message } @AsDataEndpoint(OCPP2_Namespace.MyEntity, HttpMethod.Get) async getMyEntity(request: FastifyRequest, reply: FastifyReply) { // Return data } } // 3. Export from module index export { MyModule }; export { MyModuleApi }; ``` ``` -------------------------------- ### Module Test Pattern with Vitest Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Implement a module test pattern using Vitest. Includes setup with `beforeEach` and assertion with `it` and `expect`. ```typescript import { describe, it, expect, beforeEach } from 'vitest'; describe('MyModule', () => { let module: MyModule; let config: SystemConfig; beforeEach(async () => { config = defineConfig({ /* test config */ }); module = new MyModule(); await module.initialize(config); }); it('should handle action', async () => { const response = await module.handleMyAction({}); expect(response.status).toBe('Accepted'); }); }); ``` -------------------------------- ### Create Custom Module and API Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/02-module-api.md Example structure for creating a custom module and its corresponding API class. This includes defining handlers for incoming messages and endpoints for data retrieval. ```typescript // 1. Create module class export class MyModule extends AbstractModule { @AsHandler([OCPPVersion.OCPP2_0_1], 'MyAction') async handleMyAction(req: MyActionRequest): Promise { // Handle incoming OCPP message } } // 2. Create API class export class MyModuleApi extends AbstractModuleApi { @AsMessageEndpoint('MyAction', { /* schema */ }) async myAction( identifiers: string[], request: MyActionRequest, ): Promise { // Send outbound OCPP message } @AsDataEndpoint(OCPP2_Namespace.MyEntity, HttpMethod.Get) async getMyEntity(request: FastifyRequest, reply: FastifyReply) { // Return data } } // 3. Export from module index export { MyModule }; export { MyModuleApi }; ``` -------------------------------- ### Initialize and Use tslog Logger Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Set up and utilize the tslog logger for different levels of message logging (debug, info, warn, error). Ensure the 'tslog' package is installed and imported. ```typescript import { Logger } from 'tslog'; const logger = new Logger({ name: 'MyModule' }); logger.debug('Debug message'); logger.info('Info message'); logger.warn('Warning message'); logger.error('Error message'); ``` -------------------------------- ### Export Custom Module and API Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Example of exporting a custom module and its API from the module's index file. This makes them available for registration in the core application. ```typescript // packages/core/src/modules/MyModule/src/index.ts export { MyModule }; export { MyModuleApi }; export type { IMyModuleApi } from './module/interface.js'; ``` -------------------------------- ### Create Custom Module API Class Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Example of creating an API class for a custom module, extending AbstractModuleApi. Demonstrates defining message and data endpoints using decorators. ```typescript import { AbstractModuleApi, AsMessageEndpoint, AsDataEndpoint, HttpMethod, OCPP2_Namespace, } from '@citrineos/base'; export class MyModuleApi extends AbstractModuleApi { @AsMessageEndpoint('MyCustomAction', { /* schema */ }) async myCustomAction( identifiers: string[], request: MyCustomActionRequest, callbackUrl?: string, tenantId?: string | number, ): Promise { // Send message to stations return this._module.sender.send(identifiers, { messageTypeId: MessageTypeId.CALL, messageId: uuid(), action: 'MyCustomAction', payload: request, }); } @AsDataEndpoint( OCPP2_Namespace.MyData, HttpMethod.Get, { stationId: { type: 'string' } }, ) async getMyData( request: FastifyRequest, reply: FastifyReply, ): Promise { const data = await this.repository.findOne({ stationId: request.query.stationId, }); reply.send(data); } } ``` -------------------------------- ### Throwing UnauthorizedError Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/errors.md Example of throwing an UnauthorizedError if authentication fails. ```typescript const authResult = await authProvider.authenticate(token); if (!authResult.authenticated) { throw new UnauthorizedError('Invalid authentication token'); } ``` -------------------------------- ### Run Server with Docker Compose Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/README.md Use this command to start the server and its supporting services (RabbitMQ, PostgreSQL, MinIO, Hasura) using Docker Compose. The server image is built from local source with live reload enabled. ```shell cd apps/Server docker compose up -d ``` -------------------------------- ### Throwing NotFoundError Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/errors.md Example of throwing a NotFoundError if a repository lookup fails to find an entity by its ID. ```typescript @AsDataEndpoint(namespace, HttpMethod.Get) async getEntity(request: FastifyRequest, reply: FastifyReply) { const entity = await repository.findById(request.params.id); if (!entity) { throw new NotFoundError(`Entity ${request.params.id} not found`); } reply.send(entity); } ``` -------------------------------- ### Throwing BadRequestError Example Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/errors.md Example of throwing a BadRequestError when a required 'name' field is missing from the request body. ```typescript @AsDataEndpoint(namespace, HttpMethod.Post) async createEntity(request: FastifyRequest, reply: FastifyReply) { if (!request.body.name) { throw new BadRequestError('Name field is required'); } // ... } ``` -------------------------------- ### Initialize Swagger/OpenAPI Documentation Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Initialize Swagger documentation for the Fastify server. Configure the path, logo, and data/message exposure. ```typescript import { initSwagger } from '@citrineos/core'; await initSwagger(fastifyServer, { path: '/api/docs', logoPath: '/public/logo.png', exposeData: true, exposeMessage: true, }); ``` -------------------------------- ### Run Full Stack with Docker Compose (Local Server Build) Source: https://github.com/citrineos/citrineos-core/blob/main/README.md Execute this command to start the CitrineOS stack, building the server from your local source code. This is useful for development when you need to test changes to the server. ```shell docker compose -f docker-compose.local.yml up -d ``` -------------------------------- ### Initialize Swagger/OpenAPI Documentation Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Initializes Swagger/OpenAPI documentation for a Fastify server. Configure options like the documentation path, logo, route prefix, and data/message exposure. ```typescript export async function initSwagger( server: FastifyInstance, options: SwaggerOptions, ): Promise; interface SwaggerOptions { path?: string; // Default: /docs logoPath?: string; routePrefix?: string; exposeData?: boolean; exposeMessage?: boolean; } ``` ```typescript import { initSwagger } from '@citrineos/core'; await initSwagger(fastifyServer, { path: '/api/docs', logoPath: '/public/logo.png', exposeData: true, exposeMessage: true, }); ``` -------------------------------- ### POST /data/tenant Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Create a new tenant. ```APIDOC ## POST /data/tenant ### Description Create new tenant. ### Method POST ### Endpoint /data/tenant ### Request Body - **name** (string) - Required - The name of the tenant. - **code** (string) - Optional - The code for the tenant. - **domain** (string) - Optional - The domain for the tenant. - **isActive** (boolean) - Required - Whether the tenant is active. - **features** (string[]) - Optional - A list of features enabled for the tenant. - **metadata** (Record) - Optional - Additional metadata for the tenant. ``` -------------------------------- ### Initialize Hasura Project Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/README.md Initialize a new Hasura project within the graphql-engine container. This command sets up the necessary project structure for Hasura CLI operations. ```bash hasura-cli init OR hasura init ``` -------------------------------- ### Get Certificate Details Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Fetches the details of a specific certificate using its ID. ```APIDOC ## GET /data/certificate/{id} ### Description Get certificate details. ### Method GET ### Endpoint /data/certificate/{id} ``` -------------------------------- ### GET /data/transaction/{id} Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Fetches the details of a specific transaction identified by its unique ID. ```APIDOC ## GET /data/transaction/{id} ### Description Get transaction details. ### Method GET ### Endpoint /data/transaction/{id} ### Parameters #### Path Parameters - **id** (number) - Required - Transaction ID ### Response #### Success Response (200) Single transaction object with all details. (No specific response schema provided in source) ``` -------------------------------- ### Define ChargingStation DTO Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Example of defining a ChargingStation DTO using types from '@citrineos/base'. ```typescript import type { ChargingStationDto, TransactionDto, AuthorizationDto, ChargingProfileDto, ReservationDto, LocationDto, CertificateDto, } from '@citrineos/base'; const station: ChargingStationDto = { stationId: 'CS-001', model: 'Model X', vendor: 'Vendor Y', }; ``` -------------------------------- ### Creating a Custom Module - Step 3: Export from Module Index Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Shows how to export the custom module and its API from the module's index file. ```APIDOC **Step 3: Export from Module Index** ```typescript // packages/core/src/modules/MyModule/src/index.ts export { MyModule }; export { MyModuleApi }; export type { IMyModuleApi } from './module/interface.js'; ``` ``` -------------------------------- ### Workspace Maintenance Commands Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Commands for cleaning build artifacts and performing a fresh installation of the workspace. ```bash # Workspace pnpm run clean # Remove build artifacts pnpm run fresh # Clean and reinstall ``` -------------------------------- ### Handle Custom Errors Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Example of throwing and catching custom errors like BadRequestError from '@citrineos/base'. ```typescript import { BadRequestError, NotFoundError, UnauthorizedError } from '@citrineos/base'; try { throw new BadRequestError('Invalid input'); } catch (error) { if (error instanceof BadRequestError) { console.log(error.statusCode); // 400 } } ``` -------------------------------- ### Creating a Custom Module - Step 4: Register in Core Index Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Details how to register the custom module in the core system's main index file to make it available. ```APIDOC **Step 4: Register in Core Index** ```typescript // packages/core/index.ts export * from '@modules/MyModule/src/index.js'; ``` ``` -------------------------------- ### Creating a Custom Module - Step 1: Create Module Class Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/06-core-modules.md Demonstrates how to create a custom module by extending the `AbstractModule` class and implementing custom message handlers. ```APIDOC ## Creating a Custom Module **Step 1: Create Module Class** ```typescript import { AbstractModule, AsHandler, OCPPVersion } from '@citrineos/base'; export class MyModule extends AbstractModule { name = 'MyModule'; async initialize(config: SystemConfig): Promise { await super.initialize(config); // Initialize module-specific resources } @AsHandler([OCPPVersion.OCPP2_0_1], 'MyCustomAction') async handleMyCustomAction(req: MyCustomActionRequest): Promise { // Handle incoming OCPP message this._logger.info('Handling MyCustomAction', req); return { status: 'Accepted' }; } } ``` ``` -------------------------------- ### In-Memory Cache Operations Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/QUICK-REFERENCE.md Demonstrates setting and getting values from an in-memory cache. Requires importing MemoryCache from @citrineos/core. ```typescript import { MemoryCache, RedisCache } from '@citrineos/core'; // In-memory const cache = new MemoryCache(); await cache.set('key', value, 'namespace', 3600); const cached = await cache.get('key', 'namespace'); ``` -------------------------------- ### Charging Station Status Online Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/README.MD The expected status of a charging station in the Operator UI after EVerest has been started and connected. ```nginx Online ``` -------------------------------- ### IVatProvider Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/types.md Interface for VAT (Value Added Tax) related operations, including getting VAT percentage and calculating VAT. ```APIDOC ## IVatProvider Interface ### Description Interface for VAT calculation services. ### Methods #### getVatPercentage Retrieves the VAT percentage for a given country. - **country** (string) - Required - The country code. #### calculateVat Calculates the VAT amount for a given amount and country. - **amount** (number) - Required - The base amount. - **country** (string) - Required - The country code. ``` -------------------------------- ### Navigate to Create Location in Operator UI Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/README.MD Instructions for navigating the Operator UI to create a new location. ```pgsql Locations → Create Location ``` -------------------------------- ### Get Default Sequelize Instance Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/04-data-access.md Obtain the singleton Sequelize instance for database operations. Ensure the '@citrineos/core' package is imported. ```typescript import { DefaultSequelizeInstance } from '@citrineos/core'; const sequelize = DefaultSequelizeInstance.getInstance(); ``` -------------------------------- ### INetworkConnection Interface Definition Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/types.md Defines the contract for network connections, including methods to start, stop, and check the running status. ```typescript interface INetworkConnection { start(): Promise; stop(): Promise; isRunning(): boolean; } ``` -------------------------------- ### Build Workspace Packages Source: https://github.com/citrineos/citrineos-core/blob/main/apps/operator-ui/README.MD Execute this command from the repository root to build all workspace packages. This ensures that the compiled output of packages like @citrineos/base is available for the Operator UI. ```bash # from the repository root pnpm run build ``` -------------------------------- ### ICache Interface Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/types.md An abstraction for cache operations, providing methods to get, set, delete, check existence, and flush cache entries. ```APIDOC ## ICache Interface ### Description Provides a generic interface for cache operations. It supports retrieving, storing, deleting, and checking for the existence of cached data, as well as flushing the entire cache or specific namespaces. ### Methods #### `get(key: string, namespace?: string): Promise` Retrieves a value from the cache by key. #### `set(key: string, value: T, namespace?: string, ttl?: number): Promise` Sets a value in the cache with an optional time-to-live (TTL). #### `delete(key: string, namespace?: string): Promise` Deletes a value from the cache by key. #### `exists(key: string, namespace?: string): Promise` Checks if a key exists in the cache. #### `flush(namespace?: string): Promise` Clears all entries from the cache, optionally for a specific namespace. ``` -------------------------------- ### RedisCache Configuration (Host/Port) Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/05-utilities.md Illustrates configuring the RedisCache using host and port in SystemConfig. ```typescript util: { cache: { redis: { host: 'redis.example.com', port: 6379, } } } ``` -------------------------------- ### Get Charging Profiles Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Retrieves applicable charging profiles for an Electric Vehicle (EV). This endpoint is used to fetch charging configurations. ```APIDOC ## POST /ocpp/2.0.1/evdriver/getChargingProfiles ### Description Get applicable charging profiles for EV. ### Method POST ### Endpoint /ocpp/2.0.1/evdriver/getChargingProfiles ### Request Body - **requestId** (number) - Required - **chargingProfile** (object) - Required - Charging profile details. - **chargingProfilePurpose** (string) - Required - **stackLevel** (number) - Optional - **evse** (object) - Optional - EVSE details. - **id** (number) - Required - **connectorId** (number) - Optional ``` -------------------------------- ### GET /data/transaction Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/endpoints.md Retrieves a list of all transactions, with options to paginate results using `limit` and `offset` query parameters, and filter by `tenantId`. ```APIDOC ## GET /data/transaction ### Description List all transactions. ### Method GET ### Endpoint /data/transaction ### Parameters #### Query Parameters - **limit** (number) - Optional - Results per page - **offset** (number) - Optional - Starting row - **tenantId** (string|number) - Optional - Filter by tenant ### Response #### Success Response (200) - **id** (number) - Description - **transactionId** (string) - Description - **stationId** (string) - Description - **startTime** (string) - Description - **stopTime** (string) - Description - **meterStart** (number) - Description - **meterStop** (number) - Description - **totalEnergy** (number) - Description - **tenantId** (number) - Description ### Response Example { "id": 1, "transactionId": "TX-001", "stationId": "CS-001", "startTime": "2024-01-15T10:00:00Z", "stopTime": "2024-01-15T11:30:00Z", "meterStart": 1000, "meterStop": 2150, "totalEnergy": 1150, "tenantId": 1 } ``` -------------------------------- ### Override Runtime Configuration with Environment Variables Source: https://github.com/citrineos/citrineos-core/blob/main/apps/Server/README.md Demonstrates how to override configuration values using environment variables. Environment variables prefixed with 'citrineos_' and using underscores for hierarchy override corresponding values in configuration files. This example shows overriding the amqp URL. ```json util: { (...) messageBroker: { amqp: { url: 'amqp://guest:guest@localhost:5672' (...) } (...) } (...) } ``` -------------------------------- ### ICache Interface Definition Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/types.md Provides an abstraction for cache operations, including getting, setting, deleting, checking existence, and flushing cache entries. ```typescript interface ICache { get(key: string, namespace?: string): Promise; set( key: string, value: T, namespace?: string, ttl?: number, ): Promise; delete(key: string, namespace?: string): Promise; exists(key: string, namespace?: string): Promise; flush(namespace?: string): Promise; } ``` -------------------------------- ### Configure Data Access Connection Source: https://github.com/citrineos/citrineos-core/blob/main/_autodocs/04-data-access.md Set up the connection configuration for the data access layer. Ensure 'synchronize' is false when using migrations. ```typescript const config = { dialect: 'postgres', host: 'localhost', port: 5432, database: 'citrineos', username: 'citrineos', password: 'password', logging: false, synchronize: false, // Use migrations instead repositoryMode: true, models: [ChargingStation, Transaction, /* ... */], }; ```