Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export function assert(predicate: boolean | (() => boolean), message?: string): asserts predicate {
switch (typeof predicate) {
case 'boolean': {
if (!predicate) {
throw new Error(message);
}
break;
}
case 'function': {
if (!predicate()) {
throw new Error(message);
}
break;
}
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const exhaustiveCheck: never = predicate;
}
}
}
export function notNull(object: any): boolean {
return object !== undefined && object !== null;
}
/**
* Ensures that obj2 contains all keys from obj1.
* @param obj1
* @param obj2
* @returns
*/
export function deepDirectionalEqual(obj1: any, obj2: any, seenObjects = new WeakSet()): boolean {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false;
}
// Check if obj1 has already been seen to avoid cycles
if (seenObjects.has(obj1)) {
return true; // If we've already seen obj1, we assume it's equivalent.
}
// Add obj1 to the seen set
seenObjects.add(obj1);
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
for (const key of keys1) {
if (!keys2.includes(key) || !deepDirectionalEqual(obj1[key], obj2[key], seenObjects)) {
return false;
}
}
return true;
}

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export interface BootConfig {
/**
* Also declared in SystemConfig. If absent, SystemConfig value is used.
*/
heartbeatInterval?: number | null;
/**
* Also declared in SystemConfig. If absent, SystemConfig value is used.
*/
bootRetryInterval?: number | null;
status: string;
statusInfo?: object | null;
/**
* Also declared in SystemConfig. If absent, SystemConfig value is used.
*/
getBaseReportOnPending?: boolean | null;
/**
* Ids of variable attributes to be sent in SetVariablesRequest on pending boot
*/
pendingBootSetVariableIds?: number[] | null;
/**
* Also declared in SystemConfig. If absent, SystemConfig value is used.
*/
bootWithRejectedVariables?: boolean | null;
/**
* Specifically for OCPP 1.6 which plays similar role to pendingBootSetVariableIds
*/
changeConfigurationsOnPending?: boolean | null;
/**
* Specifically for OCPP 1.6 which plays similar role to getBaseReportOnPending
*/
getConfigurationsOnPending?: boolean | null;
}
/**
* Cache boot status is used to keep track of the overall boot process for Rejected or Pending.
* When Accepting a boot, blacklist needs to be cleared if and only if there was a previously
* Rejected or Pending boot. When starting to configure charger, i.e. sending GetBaseReport or
* SetVariables, this should only be done if configuring is not still ongoing from a previous
* BootNotificationRequest. Cache boot status mediates this behavior.
*/
export const BOOT_STATUS = 'boot_status';

View File

@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { IFileStorage } from '@interfaces/files/fileStorage.js';
import type { SystemConfig } from './types.js';
export interface ConfigStore extends IFileStorage {
fetchConfig(): Promise<SystemConfig | null>;
saveConfig(config: SystemConfig): Promise<void>;
}
export class ConfigStoreFactory {
private static instance: ConfigStore | null = null;
static setConfigStore(configStorage: ConfigStore): ConfigStore {
if (this.instance === null) {
this.instance = configStorage;
} else {
console.warn('ConfigStore has already been initialized.');
}
return this.instance;
}
static getInstance(): ConfigStore {
if (this.instance === null) {
throw new Error(
'ConfigStore has not been initialized. Call ConfigStoreFactory.setConfigStore() first.',
);
}
return this.instance;
}
}

View File

@@ -0,0 +1,198 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BOOTSTRAP_CONFIG_ENV_VAR_PREFIX } from './defineConfig.js';
// Bootstrap schema contains what's needed to start the application
export const bootstrapConfigSchema = z.object({
configFileName: z.string().default('config.json'),
configDir: z.string().optional(),
// Database configuration
database: z.object({
host: z.string().default('localhost'),
port: z.number().int().positive().default(5432),
database: z.string().default('citrine'),
dialect: z.string().default('postgres'),
username: z.string().default('citrine'),
password: z.string().default('citrine'),
pool: z
.object({
max: z.number().int().positive().optional(),
min: z.number().int().nonnegative().optional(),
acquire: z.number().int().positive().optional(),
idle: z.number().int().positive().optional(),
})
.optional(),
sync: z.boolean().default(false),
alter: z.boolean().default(false),
force: z.boolean().default(false),
maxRetries: z.number().int().positive().default(3),
retryDelay: z.number().int().positive().default(1000),
ssl: z
.object({
require: z.boolean().optional(),
rejectUnauthorized: z.boolean().optional(),
ca: z.string().optional(),
})
.optional(),
}),
// File access configuration
fileAccess: z
.object({
type: z.enum(['local', 's3', 'gcp']),
local: z
.object({
defaultFilePath: z.string().default('data'),
})
.optional(),
s3: z
.object({
region: z.string().optional(),
endpoint: z.string().optional(),
defaultBucketName: z.string().default('citrineos-s3-bucket'),
s3ForcePathStyle: z.boolean().default(true),
accessKeyId: z.string().optional(),
secretAccessKey: z.string().optional(),
})
.optional(),
gcp: z
.object({
projectId: z.string(),
credentials: z.object().optional(),
})
.optional(),
})
.refine(
(obj) => {
// Ensure the selected type has corresponding config
switch (obj.type) {
case 'local':
return !!obj.local;
case 's3':
return !!obj.s3;
case 'gcp':
return !!obj.gcp;
default:
return false;
}
},
{
message: 'Configuration for the selected file access type must be provided',
},
),
});
export type BootstrapConfig = z.infer<typeof bootstrapConfigSchema>;
/**
* Helper function to load environment variables based on prefix
*/
function getEnvVarValue(key: string): string | undefined {
const envKey = `${BOOTSTRAP_CONFIG_ENV_VAR_PREFIX}${key}`.toUpperCase();
return process.env[envKey];
}
/**
* Parse a potentially JSON-formatted environment variable
*/
function parseEnvValue(value: string): any {
try {
return JSON.parse(value);
} catch {
return value;
}
}
/**
* Load bootstrap configuration from environment variables
*/
export function loadBootstrapConfig(): BootstrapConfig {
const config: Record<string, any> = {
configFileName: getEnvVarValue('config_filename') || 'config.json',
configDir: getEnvVarValue('config_dir'),
// Database configuration
database: {
host: getEnvVarValue('database_host'),
port: getEnvVarValue('database_port') && parseInt(getEnvVarValue('database_port')!, 10),
database: getEnvVarValue('database_name'),
dialect: getEnvVarValue('database_dialect'),
username: getEnvVarValue('database_username'),
password: getEnvVarValue('database_password'),
sync: getEnvVarValue('database_sync') && parseEnvValue(getEnvVarValue('database_sync')!),
alter: getEnvVarValue('database_alter') && parseEnvValue(getEnvVarValue('database_alter')!),
force: getEnvVarValue('database_force') && parseEnvValue(getEnvVarValue('database_force')!),
maxRetries:
getEnvVarValue('database_max_retries') &&
parseInt(getEnvVarValue('database_max_retries')!, 10),
retryDelay:
getEnvVarValue('database_retry_delay') &&
parseInt(getEnvVarValue('database_retry_delay')!, 10),
},
fileAccess: {
type: getEnvVarValue('file_access_type') || 'local',
},
};
const pool = {
max: getEnvVarValue('database_pool_max') && parseInt(getEnvVarValue('database_pool_max')!, 10),
min: getEnvVarValue('database_pool_min') && parseInt(getEnvVarValue('database_pool_min')!, 10),
acquire:
getEnvVarValue('database_pool_acquire') &&
parseInt(getEnvVarValue('database_pool_acquire')!, 10),
idle:
getEnvVarValue('database_pool_idle') && parseInt(getEnvVarValue('database_pool_idle')!, 10),
};
if (Object.keys(pool).length > 0) {
config.database.pool = pool;
}
const sslRequire = getEnvVarValue('database_ssl_require');
if (sslRequire !== undefined) {
config.database.ssl = {
require: parseEnvValue(sslRequire),
rejectUnauthorized:
getEnvVarValue('database_ssl_reject_unauthorized') !== undefined
? parseEnvValue(getEnvVarValue('database_ssl_reject_unauthorized')!)
: undefined,
ca: getEnvVarValue('database_ssl_ca'),
};
}
// File access configuration
switch (config.fileAccess.type) {
case 'local':
config.fileAccess.local = {
defaultFilePath: getEnvVarValue('file_access_local_default_file_path'),
};
break;
case 's3':
config.fileAccess.s3 = {
region: getEnvVarValue('file_access_s3_region'),
endpoint: getEnvVarValue('file_access_s3_endpoint'),
defaultBucketName: getEnvVarValue('file_access_s3_default_bucket_name'),
s3ForcePathStyle:
getEnvVarValue('file_access_s3_force_path_style') &&
parseEnvValue(getEnvVarValue('file_access_s3_force_path_style')!),
accessKeyId: getEnvVarValue('file_access_s3_access_key_id'),
secretAccessKey: getEnvVarValue('file_access_s3_secret_access_key'),
};
break;
case 'gcp':
config.fileAccess.gcp = {
projectId: getEnvVarValue('file_access_gcp_project_id'),
credentials: getEnvVarValue('file_access_gcp_credentials'),
};
break;
}
try {
return bootstrapConfigSchema.parse(config);
} catch (error) {
console.error('Bootstrap configuration validation failed:', error);
throw error;
}
}

View File

@@ -0,0 +1,170 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import type { SystemConfig, SystemConfigInput } from './types.js';
import { systemConfigSchema } from './types.js';
const args = typeof process !== 'undefined' && process.argv ? process.argv.slice(2) : [];
let dynamicPrefix = 'citrineos_';
for (const arg of args) {
if (arg.startsWith('--env-prefix=')) {
dynamicPrefix = arg.split('=')[1].toLowerCase();
break;
}
}
export const CITRINE_ENV_VAR_PREFIX = dynamicPrefix;
export const BOOTSTRAP_CONFIG_ENV_VAR_PREFIX = `bootstrap_${CITRINE_ENV_VAR_PREFIX}`;
/**
* Finds a case-insensitive match for a key in an object.
* @param obj The object to search.
* @param targetKey The target key.
* @returns The matching key or undefined.
*/
function findCaseInsensitiveMatch<T>(
obj: Record<string, T>,
targetKey: string,
): string | undefined {
const lowerTargetKey = targetKey.toLowerCase();
return Object.keys(obj).find((key) => key.toLowerCase() === lowerTargetKey);
}
const getZodSchemaKeyMap = (schema: z.ZodTypeAny): Record<string, any> => {
if (schema instanceof z.ZodNullable || schema instanceof z.ZodOptional) {
return getZodSchemaKeyMap((schema as z.ZodNullable<any> | z.ZodOptional<any>).unwrap());
}
if (schema instanceof z.ZodArray) {
return getZodSchemaKeyMap(schema.element as z.ZodTypeAny);
}
if (schema instanceof z.ZodObject) {
const entries = Object.entries<z.ZodType>(schema.shape);
return entries.reduce(
(acc, [key, value]) => {
const nested = getZodSchemaKeyMap(value);
if (Object.keys(nested).length > 0) {
acc[key] = nested;
} else {
acc[key.toLowerCase()] = key;
}
return acc;
},
{} as Record<string, any>,
);
}
return {};
};
/**
* Merges configuration from environment variables into the default configuration. Allows any to keep it as generic as possible.
* @param defaultConfig The default configuration.
* @param envVars The environment variables.
* @returns The merged configuration.
*/
function mergeConfigFromEnvVars<T extends Record<string, any>>(
defaultConfig: T,
envVars: NodeJS.ProcessEnv,
configKeyMap: Record<string, any>,
): T {
const config: T = { ...defaultConfig };
const errors: string[] = [];
for (const [fullEnvKey, value] of Object.entries(envVars)) {
if (!value) {
continue;
}
const lowercaseEnvKey = fullEnvKey.toLowerCase();
if (lowercaseEnvKey.startsWith(CITRINE_ENV_VAR_PREFIX)) {
const envKeyWithoutPrefix = lowercaseEnvKey.substring(CITRINE_ENV_VAR_PREFIX.length);
const path = envKeyWithoutPrefix.split('_');
let currentConfigPart: Record<string, any> = config;
let currentConfigKeyMap: Record<string, any> = configKeyMap;
let validMapping = true;
for (let i = 0; i < path.length - 1; i++) {
const part = path[i];
const matchingKey = findCaseInsensitiveMatch(currentConfigKeyMap, part);
if (!matchingKey) {
errors.push(
`Environment variable '${fullEnvKey}' refers to unknown configuration segment '${part}'.`,
);
validMapping = false;
break;
}
if (currentConfigPart[matchingKey] === undefined) {
currentConfigPart[matchingKey] = {};
} else if (
typeof currentConfigPart[matchingKey] !== 'object' ||
currentConfigPart[matchingKey] === null
) {
errors.push(
`Environment variable '${fullEnvKey}' refers to configuration segment '${part}', but its current value is not an object.`,
);
validMapping = false;
break;
}
currentConfigPart = currentConfigPart[matchingKey];
currentConfigKeyMap = currentConfigKeyMap[matchingKey];
}
if (!validMapping) {
continue;
}
const finalPart = path[path.length - 1];
const keyToUse = currentConfigKeyMap[finalPart.toLowerCase()] || finalPart;
try {
currentConfigPart[keyToUse] = JSON.parse(value as string);
} catch {
console.debug(`Mapping '${value}' as string for environment variable '${fullEnvKey}'.`);
currentConfigPart[keyToUse] = value;
}
}
}
errors.forEach((err) => console.error(err));
return config as T;
}
/**
* Validates the system configuration to ensure required properties are set.
* @param finalConfig The final system configuration.
* @throws Error if required properties are not set.
*/
function validateFinalConfig(finalConfig: SystemConfigInput): SystemConfig {
return systemConfigSchema.parse(finalConfig);
}
/**
* Defines the application configuration by merging input configuration which is defined in a file with environment variables.
* Takes environment variables over predefined
* @param inputConfig The file defined input configuration.
* @returns The final system configuration.
* @throws Error if required environment variables are not set or if there are parsing errors.
*/
export function defineConfig(inputConfig: SystemConfigInput): SystemConfig {
const configKeyMap: Record<string, any> = getZodSchemaKeyMap(systemConfigSchema);
const appConfig = mergeConfigFromEnvVars<SystemConfigInput>(
inputConfig,
process.env,
configKeyMap,
);
return validateFinalConfig(appConfig);
}
export const DEFAULT_TENANT_ID = 1;

View File

@@ -0,0 +1,670 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { RegistrationStatusEnum } from '@interfaces/dto/types/enums.js';
import { EventGroup } from '@interfaces/messages/internal-types.js';
import { OCPP1_6 } from '@ocpp/model/index.js';
import { OCPP_CallAction, OCPPVersion, type OCPPVersionType } from '@ocpp/rpc/message.js';
import { z } from 'zod';
const CallActionSchema = z.nativeEnum(OCPP_CallAction);
export const oidcClientConfigSchema = z
.object({
tokenUrl: z.string(),
clientId: z.string(),
clientSecret: z.string(),
audience: z.string(),
})
.optional();
export const OCPP_VERSION_LIST: OCPPVersionType[] = [
OCPPVersion.OCPP2_1,
OCPPVersion.OCPP2_0_1,
OCPPVersion.OCPP1_6,
] as const;
const signedMeterValuesSigningMethods = ['RSASSA-PKCS1-v1_5', 'ECDSA', 'SECP192R1'] as const;
// TODO: Refactor other objects out of system config, such as certificatesModuleInputSchema etc.
export const websocketServerInputSchema = z.object({
id: z.string().optional(),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8080).optional(),
pingInterval: z.number().int().min(1).default(60).optional(),
protocols: z.array(z.enum(OCPP_VERSION_LIST)).default(['ocpp2.0.1']).optional(),
securityProfile: z.number().int().min(0).max(3).default(0).optional(),
allowUnknownChargingStations: z.boolean().default(false).optional(),
ignoreAuthenticationHeaders: z.boolean().default(false).optional(), // When true, authorization headers will be ignored and authentication will be bypassed.
tlsKeyFilePath: z.string().optional(), // Leaf certificate's private key pem which decrypts the message from client
tlsCertificateChainFilePath: z.string().optional(), // Certificate chain pem consist of a leaf followed by sub CAs
mtlsCertificateAuthorityKeyFilePath: z.string().optional(), // Sub CA's private key which signs the leaf (e.g.,
// charging station certificate and csms certificate)
rootCACertificateFilePath: z.string().optional(), // Root CA certificate that overrides default CA certificates
// allowed by Mozilla
tenantId: z.number().optional(),
// Mapping from path segments to tenant IDs.
// Example: { "my-tenant": 1 }
tenantPathMapping: z.record(z.string(), z.number()).optional(),
// When true, tenant can be resolved at connection upgrade time from the request
// (query param, path segment, or header). Defaults to false for strict per-server tenant.
dynamicTenantResolution: z.boolean().optional().default(false),
// Forces a set protocol to communicate on, mostly used for dev purposes
forceProtocol: z.enum(OCPP_VERSION_LIST).optional(),
});
export const HUBJECT_DEFAULT_BASEURL = 'https://open.plugncharge-test.hubject.com';
export const HUBJECT_DEFAULT_TOKENURL =
'https://hubject.stoplight.io/api/v1/projects/cHJqOjk0NTg5/nodes/6bb8b3bc79c2e-authorization-token';
export const HUBJECT_DEFAULT_CLIENTID = 'YOUR_CLIENT_ID';
export const HUBJECT_DEFAULT_CLIENTSECRET = 'YOUR_CLIENT_SECRET';
export const systemConfigInputSchema = z.object({
env: z.enum(['development', 'production']),
centralSystem: z.object({
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
}),
modules: z.object({
certificates: z
.object({
endpointPrefix: z.string().default(EventGroup.Certificates).optional(),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
})
.optional(),
configuration: z.object({
heartbeatInterval: z.number().int().min(1).default(60).optional(),
bootRetryInterval: z.number().int().min(1).default(10).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
ocpp2_0_1: z
.object({
unknownChargerStatus: z
.enum([
RegistrationStatusEnum.Accepted,
RegistrationStatusEnum.Pending,
RegistrationStatusEnum.Rejected,
])
.default(RegistrationStatusEnum.Accepted)
.optional(), // Unknown chargers have no entry in BootConfig table
getBaseReportOnPending: z.boolean().default(true).optional(),
bootWithRejectedVariables: z.boolean().default(true).optional(),
autoAccept: z.boolean().default(true).optional(), // If false, only data endpoint can update boot status to accepted
})
.optional(),
ocpp2_1: z
.object({
unknownChargerStatus: z
.enum([
RegistrationStatusEnum.Accepted,
RegistrationStatusEnum.Pending,
RegistrationStatusEnum.Rejected,
])
.default(RegistrationStatusEnum.Accepted)
.optional(), // Unknown chargers have no entry in BootConfig table
getBaseReportOnPending: z.boolean().default(true).optional(),
bootWithRejectedVariables: z.boolean().default(true).optional(),
autoAccept: z.boolean().default(true).optional(), // If false, only data endpoint can update boot status to accepted
})
.optional(),
ocpp1_6: z
.object({
unknownChargerStatus: z
.enum([
OCPP1_6.BootNotificationResponseStatus.Accepted,
OCPP1_6.BootNotificationResponseStatus.Pending,
OCPP1_6.BootNotificationResponseStatus.Rejected,
])
.default(OCPP1_6.BootNotificationResponseStatus.Accepted)
.optional(), // Unknown chargers have no entry in BootConfig table
})
.optional(),
endpointPrefix: z.string().default(EventGroup.Configuration).optional(),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
}),
evdriver: z.object({
endpointPrefix: z.string().default(EventGroup.EVDriver).optional(),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
enableGetChargingProfilesOnStartTransaction: z.boolean().default(true).optional(),
}),
monitoring: z.object({
endpointPrefix: z.string().default(EventGroup.Monitoring).optional(),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
}),
reporting: z.object({
endpointPrefix: z.string().default(EventGroup.Reporting).optional(),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
}),
smartcharging: z
.object({
endpointPrefix: z.string().default(EventGroup.SmartCharging).optional(),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
})
.optional(),
tenant: z
.object({
endpointPrefix: z.string().default(EventGroup.Tenant).optional(),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
ocppRouterBaseUrl: z.string().optional(),
})
.optional(),
transactions: z.object({
endpointPrefix: z.string().default(EventGroup.Transactions).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8081).optional(),
costUpdatedInterval: z.number().int().min(1).default(60).optional(),
sendCostUpdatedOnMeterValue: z.boolean().default(false).optional(),
signedMeterValuesConfiguration: z
.object({
publicKeyFileId: z.string(),
signingMethod: z.enum(signedMeterValuesSigningMethods),
rejectUnsupportedSignedMeterValues: z.boolean().default(false).optional(),
})
.optional(),
/** Base URL for generating receipt URLs when ReceiptByCSMS is true (C21). */
receiptBaseUrl: z.string().optional(),
}),
}),
util: z.object({
cache: z
.object({
memory: z.boolean().optional(),
redis: z
.union([
z.object({
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(6379).optional(),
}),
z.object({
url: z.url().refine((v) => v.startsWith('redis://') || v.startsWith('rediss://'), {
message: 'Redis URL must start with redis:// or rediss://',
}),
}),
])
.optional(),
})
.refine((obj) => obj.memory || obj.redis, {
message: 'A cache implementation must be set',
}),
messageBroker: z
.object({
amqp: z
.object({
url: z.string(),
exchange: z.string(),
instanceIdentifier: z.string().optional(),
})
.optional(),
})
.refine((obj) => obj.amqp, {
message: 'A message broker implementation must be set',
}),
authProvider: z
.object({
oidc: z
.object({
jwksUri: z.string(),
issuer: z.string(),
audience: z.string(),
cacheTime: z.number().int().min(1).optional(),
rateLimit: z.boolean().default(false).optional(),
})
.optional(),
localByPass: z.boolean().default(false).optional(),
})
.refine((obj) => obj.oidc || obj.localByPass, {
message: 'An auth provider implementation must be set',
}),
swagger: z
.object({
path: z.string().default('/docs').optional(),
logoPath: z.string(),
exposeData: z.boolean().default(true).optional(),
exposeMessage: z.boolean().default(true).optional(),
})
.optional(),
networkConnection: z.object({
websocketServers: z.array(websocketServerInputSchema.optional()),
}),
certificateAuthority: z.object({
v2gCA: z
.object({
name: z.enum(['hubject']).default('hubject'),
hubject: z
.object({
baseUrl: z.string().default(HUBJECT_DEFAULT_BASEURL),
tokenUrl: z.string().default(HUBJECT_DEFAULT_TOKENURL),
clientId: z.string().default(HUBJECT_DEFAULT_CLIENTID),
clientSecret: z.string().default(HUBJECT_DEFAULT_CLIENTSECRET),
})
.optional(),
})
.refine(
(obj) => {
if (obj.name === 'hubject') {
return (
obj.hubject &&
obj.hubject.baseUrl &&
obj.hubject.tokenUrl &&
obj.hubject.clientId &&
obj.hubject.clientSecret
);
} else {
return false;
}
},
{
message: 'Hubject requires baseUrl, tokenUrl, clientId, and clientSecret',
},
),
chargingStationCA: z
.object({
name: z.enum(['acme']).default('acme'),
acme: z
.object({
env: z.enum(['staging', 'production']).default('staging'),
accountKeyFilePath: z.string(),
email: z.string(),
})
.optional(),
})
.refine((obj) => {
if (obj.name === 'acme') {
return obj.acme;
} else {
return false;
}
}),
}),
}),
logLevel: z.number().min(0).max(6).default(0).optional(),
maxCallLengthSeconds: z.number().int().min(1).default(20).optional(),
maxCachingSeconds: z.number().int().min(1).default(30).optional(),
maxReconnectDelay: z.number().int().min(1).default(30).optional(),
shutdownGracePeriodSeconds: z.number().int().min(1).default(30).optional(),
ocpiServer: z.object({
host: z.string().default('localhost').optional(),
port: z.number().int().min(1).default(8085).optional(),
}),
userPreferences: z.object({
telemetryConsent: z.boolean().default(false).optional(),
}),
rbacRulesFileName: z.string().default('rbac-rules.json').optional(),
rbacRulesDir: z.string().optional(),
realTimeAuthDefaultTimeoutSeconds: z.number().int().min(1).default(15).optional(),
notReadyThresholdSeconds: z.number().int().min(1).default(60).optional(),
});
export type SystemConfigInput = z.infer<typeof systemConfigInputSchema>;
export const websocketServerSchema = z
.object({
id: z.string(),
host: z.string(),
port: z.number().int().min(1),
pingInterval: z.number().int().min(1),
protocols: z.array(z.enum(OCPP_VERSION_LIST)),
securityProfile: z.number().int().min(0).max(3),
allowUnknownChargingStations: z.boolean(),
ignoreAuthenticationHeaders: z.boolean().default(false).optional(),
tlsKeyFilePath: z.string().optional(),
tlsCertificateChainFilePath: z.string().optional(),
mtlsCertificateAuthorityKeyFilePath: z.string().optional(),
rootCACertificateFilePath: z.string().optional(),
tenantId: z.number().optional(),
tenantPathMapping: z.record(z.string(), z.number()).optional(),
// When true, tenant can be resolved at connection upgrade time from the request
// (query param, path segment, or header). Defaults to false for strict per-server tenant.
dynamicTenantResolution: z.boolean().optional().default(false),
forceProtocol: z.enum(OCPP_VERSION_LIST).optional(),
})
.refine((obj) => {
switch (obj.securityProfile) {
case 0: // No security
case 1: // Basic Auth
return true;
case 2: // Basic Auth + TLS
return obj.tlsKeyFilePath && obj.tlsCertificateChainFilePath;
case 3: // mTLS
return (
obj.tlsCertificateChainFilePath &&
obj.tlsKeyFilePath &&
obj.mtlsCertificateAuthorityKeyFilePath
);
default:
return false;
}
})
.refine((obj) => {
if ((obj.tenantId !== undefined) === obj.dynamicTenantResolution) {
return false; // Cannot have both or neither tenantId and dynamicTenantResolution
} else {
return true;
}
}, 'Invalid websocket server configuration: tenantId and dynamicTenantResolution are mutually exclusive and one must be set');
export const systemConfigSchema = z
.object({
env: z.enum(['development', 'production']),
centralSystem: z.object({
host: z.string(),
port: z.number().int().min(1),
}),
modules: z.object({
certificates: z
.object({
endpointPrefix: z.string(),
host: z.string().optional(),
port: z.number().int().min(1).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
})
.optional(),
evdriver: z.object({
endpointPrefix: z.string(),
host: z.string().optional(),
port: z.number().int().min(1).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
enableGetChargingProfilesOnStartTransaction: z.boolean().optional(),
}),
configuration: z
.object({
heartbeatInterval: z.number().int().min(1),
bootRetryInterval: z.number().int().min(1),
ocpp2_0_1: z
.object({
unknownChargerStatus: z.enum([
RegistrationStatusEnum.Accepted,
RegistrationStatusEnum.Pending,
RegistrationStatusEnum.Rejected,
]), // Unknown chargers have no entry in BootConfig table
getBaseReportOnPending: z.boolean(),
bootWithRejectedVariables: z.boolean(),
/**
* If false, only data endpoint can update boot status to accepted
*/
autoAccept: z.boolean(),
})
.optional(),
ocpp2_1: z
.object({
unknownChargerStatus: z.enum([
RegistrationStatusEnum.Accepted,
RegistrationStatusEnum.Pending,
RegistrationStatusEnum.Rejected,
]), // Unknown chargers have no entry in BootConfig table
getBaseReportOnPending: z.boolean(),
bootWithRejectedVariables: z.boolean(),
/**
* If false, only data endpoint can update boot status to accepted
*/
autoAccept: z.boolean(),
})
.optional(),
ocpp1_6: z
.object({
unknownChargerStatus: z.enum([
OCPP1_6.BootNotificationResponseStatus.Accepted,
OCPP1_6.BootNotificationResponseStatus.Pending,
OCPP1_6.BootNotificationResponseStatus.Rejected,
]), // Unknown chargers have no entry in BootConfig table
})
.optional(),
endpointPrefix: z.string(),
host: z.string().optional(),
port: z.number().int().min(1).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
})
.refine((obj) => obj.ocpp1_6 || obj.ocpp2_0_1 || obj.ocpp2_1, {
message: 'A protocol configuration must be set',
}), // Configuration module is required
monitoring: z.object({
endpointPrefix: z.string(),
host: z.string().optional(),
port: z.number().int().min(1).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
}),
reporting: z.object({
endpointPrefix: z.string(),
host: z.string().optional(),
port: z.number().int().min(1).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
}),
smartcharging: z
.object({
endpointPrefix: z.string(),
host: z.string().optional(),
port: z.number().int().min(1).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
})
.optional(),
tenant: z.object({
endpointPrefix: z.string(),
host: z.string().optional(),
port: z.number().int().min(1).optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
ocppRouterBaseUrl: z.string().optional(),
}),
transactions: z
.object({
endpointPrefix: z.string(),
host: z.string().optional(),
port: z.number().int().min(1).optional(),
costUpdatedInterval: z.number().int().min(1).optional(),
sendCostUpdatedOnMeterValue: z.boolean().optional(),
requests: z.array(CallActionSchema),
responses: z.array(CallActionSchema),
signedMeterValuesConfiguration: z
.object({
publicKeyFileId: z.string(),
signingMethod: z.enum(signedMeterValuesSigningMethods),
rejectUnsupportedSignedMeterValues: z.boolean().optional(),
})
.optional(),
/** Base URL for generating receipt URLs when ReceiptByCSMS is true (C21). */
receiptBaseUrl: z.string().optional(),
})
.refine(
(obj) =>
!(obj.costUpdatedInterval && obj.sendCostUpdatedOnMeterValue) &&
(obj.costUpdatedInterval || obj.sendCostUpdatedOnMeterValue),
{
message:
'Can only update cost based on the interval or in response to a transaction event /meter value' +
' update. Not allowed to have both costUpdatedInterval and sendCostUpdatedOnMeterValue configured',
},
),
}),
util: z.object({
cache: z
.object({
memory: z.boolean().optional(),
redis: z
.union([
z.object({
host: z.string(),
port: z.number().int().min(1),
}),
z.object({
url: z.url().refine((v) => v.startsWith('redis://') || v.startsWith('rediss://'), {
message: 'Redis URL must start with redis:// or rediss://',
}),
}),
])
.optional(),
})
.refine((obj) => obj.memory || obj.redis, {
message: 'A cache implementation must be set',
}),
messageBroker: z
.object({
amqp: z
.object({
url: z.string(),
exchange: z.string(),
instanceIdentifier: z.string().optional(),
})
.optional(),
})
.refine((obj) => obj.amqp, {
message: 'A message broker implementation must be set',
}),
authProvider: z
.object({
oidc: z
.object({
jwksUri: z.string(),
issuer: z.string(),
audience: z.string(),
cacheTime: z.number().int().min(1).optional(),
rateLimit: z.boolean(),
})
.optional(),
localByPass: z.boolean().default(false).optional(),
})
.refine((obj) => obj.oidc || obj.localByPass, {
message: 'An auth provider implementation must be set',
}),
swagger: z
.object({
path: z.string(),
logoPath: z.string(),
exposeData: z.boolean(),
exposeMessage: z.boolean(),
})
.optional(),
networkConnection: z.object({
websocketServers: z.array(websocketServerSchema).refine((array) => {
const idsSeen = new Set<string>();
return array.filter((obj) => {
if (idsSeen.has(obj.id)) {
return false;
} else {
idsSeen.add(obj.id);
return true;
}
});
}),
}),
certificateAuthority: z.object({
v2gCA: z
.object({
name: z.enum(['hubject']),
hubject: z
.object({
baseUrl: z.string(),
tokenUrl: z.string(),
clientId: z.string(),
clientSecret: z.string(),
})
.optional(),
})
.refine(
(obj) => {
if (obj.name === 'hubject') {
return (
obj.hubject &&
obj.hubject.baseUrl &&
obj.hubject.tokenUrl &&
obj.hubject.clientId &&
obj.hubject.clientSecret
);
} else {
return false;
}
},
{
message: 'Hubject requires baseUrl, tokenUrl, clientId, and clientSecret',
},
),
chargingStationCA: z
.object({
name: z.enum(['acme']),
acme: z
.object({
env: z.enum(['staging', 'production']),
accountKeyFilePath: z.string(),
email: z.string(),
})
.optional(),
})
.refine((obj) => {
if (obj.name === 'acme') {
return obj.acme;
} else {
return false;
}
}),
}),
}),
logLevel: z.number().min(0).max(6),
maxCallLengthSeconds: z.number().int().min(1),
maxCachingSeconds: z.number().int().min(1),
maxReconnectDelay: z.number().int().min(1).default(30),
shutdownGracePeriodSeconds: z.number().int().min(1).default(30),
ocpiServer: z.object({
host: z.string(),
port: z.number().int().min(1),
}),
userPreferences: z.object({
telemetryConsent: z.boolean().optional(),
}),
rbacRulesFileName: z.string().optional(),
rbacRulesDir: z.string().optional(),
oidcClient: oidcClientConfigSchema,
realTimeAuthDefaultTimeoutSeconds: z.number().int().min(1).default(15),
notReadyThresholdSeconds: z.number().int().min(1).default(60),
})
.refine((obj) => obj.maxCachingSeconds >= obj.maxCallLengthSeconds, {
message: 'maxCachingSeconds cannot be less than maxCallLengthSeconds',
});
export const HttpMethodSchema = z.record(
z.string(), // HTTP method (GET, POST, etc., or * for all methods)
z.array(z.string()), // Array of role names required for this method
);
export const UrlPatternSchema = z.record(
z.string(), // URL pattern (/api/users, /api/users/:id, etc.)
HttpMethodSchema,
);
export const TenantSchema = z.record(
z.string(), // Tenant ID
UrlPatternSchema,
);
export const RbacRulesSchema = TenantSchema;
export type RbacRules = z.infer<typeof RbacRulesSchema>;
export type WebsocketServerConfig = z.infer<typeof websocketServerSchema>;
export type SystemConfig = z.infer<typeof systemConfigSchema>;

View File

@@ -0,0 +1,488 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import 'reflect-metadata';
import type { ILogObj } from 'tslog';
import { Logger } from 'tslog';
import type { IDataEndpointDefinition } from './DataEndpointDefinition.js';
import type { IMessageEndpointDefinition } from '@interfaces/api/MessageEndpointDefinition.js';
import { METADATA_DATA_ENDPOINTS, METADATA_MESSAGE_ENDPOINTS } from '@interfaces/api/metadata.js';
import { HttpMethod } from '@interfaces/api/HttpMethods.js';
import type { SystemConfig } from '@config/types.js';
import type { OcppRequest } from '@ocpp/internal-types.js';
import { ConfigStoreFactory } from '@config/ConfigStore.js';
import { MessageConfirmationSchema } from '@ocpp/persistence/querySchema.js';
import { Namespace, OCPP1_6_Namespace } from '@ocpp/persistence/namespace.js';
import { OCPPVersion } from '@ocpp/rpc/message.js';
import { systemConfigSchema } from '@config/types.js';
import { OCPP2_Namespace } from '@ocpp/persistence/index.js';
import type { CallAction } from '@ocpp/rpc/message.js';
import type { IMessageConfirmation } from '@interfaces/messages/index.js';
import type { IModule } from '@interfaces/modules/Module.js';
import { IMessageQuerystringSchema } from '@interfaces/api/MessageQuerystring.js';
import type { IModuleApi } from '@interfaces/api/ModuleApi.js';
import { AuthorizationSecurity } from '@interfaces/api/AuthorizationSecurity.js';
import { z } from 'zod';
/**
* Abstract module api class implementation.
*/
export abstract class AbstractModuleApi<T extends IModule> implements IModuleApi {
protected readonly _server: FastifyInstance;
protected readonly _module: T;
protected readonly _logger: Logger<ILogObj>;
protected readonly _ocppVersion: OCPPVersion | null;
constructor(
module: T,
server: FastifyInstance,
ocppVersion: OCPPVersion | null,
logger?: Logger<ILogObj>,
) {
this._module = module;
this._server = server;
this._ocppVersion = ocppVersion;
this._logger = logger
? logger.getSubLogger({ name: this.constructor.name })
: new Logger<ILogObj>({ name: this.constructor.name });
this._init(this._module);
}
/**
* Initializes the API for the given module.
*
* @param {T} module - The module to initialize the API for.
*/
protected _init(module: T): void {
(
Reflect.getMetadata(
METADATA_MESSAGE_ENDPOINTS,
this.constructor,
) as Array<IMessageEndpointDefinition>
)?.forEach((expose) => {
this._addMessageRoute.call(
this,
expose.action,
expose.method,
typeof expose.bodySchema === 'function' ? expose.bodySchema(this) : expose.bodySchema,
expose.optionalQuerystrings,
);
});
const dataEndpointDefinitions = Reflect.getMetadata(
METADATA_DATA_ENDPOINTS,
this.constructor,
) as Array<IDataEndpointDefinition>;
dataEndpointDefinitions?.forEach((expose) => {
this._addDataRoute.call(
this,
expose.namespace,
expose.method,
expose.httpMethod,
expose.querySchema,
expose.paramSchema,
expose.headerSchema,
expose.bodySchema,
expose.responseSchema,
expose.tags,
expose.description,
expose.security,
);
});
if (dataEndpointDefinitions && dataEndpointDefinitions.length > 0) {
this.registerSystemConfigRoutes(module);
}
}
/**
* Add a message route to the server.
*
* @param {CallAction} action - The action to be called.
* @param {Function} method - The method to be executed.
* @param {object} bodySchema - The schema for the route.
* @param {Record<string, any>} optionalQuerystrings - Optional querystrings for the route.
* @return {void}
*/
protected _addMessageRoute(
action: CallAction,
method: (...args: any[]) => any,
bodySchema: object,
optionalQuerystrings?: Record<string, any>,
): void {
if (!bodySchema) {
this._logger.debug(
`Skipping message route for ${action} — schema not available for ${this._ocppVersion}`,
);
return;
}
this._logger.debug(`Adding message route for ${action}`, this._toMessagePath(action));
/**
* Executes the handler function for the given request.
*
* @param {FastifyRequest<{ Body: OcppRequest, Querystring: IMessageQuerystring }>} request - The request object containing the body and querystring.
* @return {Promise<IMessageConfirmation>} The promise that resolves to the message confirmation.
*/
const _handler = async (
request: FastifyRequest<{
Body: OcppRequest;
Querystring: Record<string, any>;
}>,
): Promise<IMessageConfirmation[]> => {
const { identifier, tenantId, callbackUrl, ...extraQueries } = request.query;
const identifiers = Array.isArray(identifier) ? identifier : [identifier];
return method.call(
this,
identifiers,
request.body,
callbackUrl,
tenantId,
Object.keys(extraQueries).length > 0 ? extraQueries : undefined,
);
};
const mergedQuerySchema = {
...IMessageQuerystringSchema,
properties: {
...IMessageQuerystringSchema.properties,
...(optionalQuerystrings || {}),
},
};
const _opts: any = {
method: HttpMethod.Post,
url: this._toMessagePath(action),
handler: _handler,
schema: {
body: bodySchema,
querystring: mergedQuerySchema,
response: {
200: {
$id: 'MessageConfirmationSchemaArray',
type: 'array',
items: MessageConfirmationSchema,
},
},
} as const,
};
if (this._module.config.util.swagger?.exposeMessage) {
this._server.register(async (fastifyInstance) => {
this.registerSchemaForOpts(fastifyInstance, _opts);
fastifyInstance.route(_opts);
});
} else {
this._server.route(_opts);
}
}
/**
* Add a message route to the server.
*
* @param {OCPP2_Namespace | OCPP1_6_Namespace | Namespace} namespace - The entity type.
* @param {Function} method - The method to be executed.
* @param {HttpMethod} httpMethod - The HTTP method to be used.
* @param {object} querySchema - The schema for the querystring.
* @param {object} paramSchema - The schema for the parameters.
* @param {object} headerSchema - The schema for the headers.
* @param {object} bodySchema - The schema for the body.
* @param {object} responseSchema - The schema for the response.
* @param {string[]} tags - The tags for the route.
* @param {string} description - The description for the route.
* @param {object[]} security - The security for the route.
* @return {void}
*/
protected _addDataRoute(
namespace: OCPP2_Namespace | OCPP1_6_Namespace | Namespace,
method: (...args: any[]) => any,
httpMethod: HttpMethod,
querySchema?: object,
paramSchema?: object,
headerSchema?: object,
bodySchema?: object,
responseSchema?: object,
tags?: string[],
description?: string,
security?: object[],
): void {
this._logger.debug(
`Adding data route for ${namespace}`,
this._toDataPath(namespace),
httpMethod,
);
const schema: Record<string, any> = {};
if (querySchema) {
schema['querystring'] = querySchema;
}
if (bodySchema) {
schema['body'] = bodySchema;
}
if (paramSchema) {
schema['params'] = paramSchema;
}
if (headerSchema) {
schema['headers'] = headerSchema;
}
if (responseSchema) {
schema['response'] = {
200: responseSchema,
};
}
if (tags) {
schema['tags'] = tags;
}
if (description) {
schema['description'] = description;
}
if (security) {
schema['security'] = security;
}
/**
* Handles the request and returns a Promise resolving to an object.
*
* @param {FastifyRequest<{ Body: object, Querystring: object }>} request - the request object
* @param {FastifyReply} reply - the reply object
* @return {Promise<any>} - a Promise resolving to an object
*/
const _handler = async (
request: FastifyRequest<{
Body: object;
Querystring: object;
}>,
reply: FastifyReply,
): Promise<unknown> =>
(method.call(this, request, reply) as Promise<undefined | string | object>).catch((err) => {
// TODO: figure out better error codes & messages
this._logger.error('Error in handling data route', err);
const statusCode = err.statusCode ? err.statusCode : 500;
reply.status(statusCode).send(err);
});
const _opts: any = {
method: httpMethod,
url: this._toDataPath(namespace),
schema: schema,
handler: _handler,
};
if (
!!schema &&
!!schema.headers &&
!!schema.headers.properties &&
!!schema.headers.properties.Authorization
) {
_opts['preHandler'] = (this._server as unknown as any).auth([
(this._server as unknown as any).authorization,
]);
if (!_opts['security']) {
_opts.schema['security'] = [AuthorizationSecurity];
} else {
_opts.schema['security'].push(AuthorizationSecurity);
}
}
if (this._module.config.util.swagger?.exposeData) {
this._server.register(async (fastifyInstance) => {
this.registerSchemaForOpts(fastifyInstance, _opts);
fastifyInstance.route<{ Body: object; Querystring: object }>(_opts);
});
} else {
this._server.route<{ Body: object; Querystring: object }>(_opts);
}
}
private registerSchemaForOpts = (fastifyInstance: FastifyInstance, _opts: any) => {
if (_opts.schema['querystring']) {
_opts.schema['querystring'] = this.registerSchema(
fastifyInstance,
_opts.schema['querystring'],
);
}
if (_opts.schema['body']) {
_opts.schema['body'] = this.registerSchema(
fastifyInstance,
_opts.schema['body'],
this._ocppVersion ? `${this._ocppVersion}-` : '',
);
}
if (_opts.schema['params']) {
_opts.schema['params'] = this.registerSchema(fastifyInstance, _opts.schema['params']);
}
if (_opts.schema['headers']) {
_opts.schema['headers'] = this.registerSchema(fastifyInstance, _opts.schema['headers']);
}
if (_opts.schema['response']) {
_opts.schema['response'] = {
200: this.registerSchema(fastifyInstance, _opts.schema['response'][200]),
};
}
};
protected registerSchema = (
fastifyInstance: FastifyInstance,
schema: any,
schemaIdPrefix?: string,
): object | null => {
let id = schema['$id'];
if (!id) {
this._logger.error('Could not register schema because no ID', schema);
}
try {
const schemaCopy = this.removeUnknownKeys(schema);
if (id && schemaIdPrefix) {
id = schemaIdPrefix + id;
schemaCopy['$id'] = id;
this._logger.debug(`Update schema id: ${schemaCopy['$id']}`);
}
if (
schemaCopy.required &&
Array.isArray(schemaCopy.required) &&
schemaCopy.required.length === 0
) {
delete schemaCopy.required;
}
if (schema.definitions) {
Object.keys(schema.definitions).forEach((key) => {
const definition = schema.definitions[key];
if (!definition['$id']) {
definition['$id'] = key;
}
this.registerSchema(fastifyInstance, definition);
});
}
if (schemaCopy.properties) {
Object.keys(schemaCopy.properties).forEach((key) => {
const property = schemaCopy.properties[key];
if (property.$ref) {
property.$ref = property.$ref.replace('#/definitions/', '');
}
if (property.items && property.items.$ref) {
property.items.$ref = property.items.$ref.replace('#/definitions/', '');
}
});
}
fastifyInstance.addSchema(schemaCopy);
this._server.addSchema(schemaCopy);
return {
$ref: `${id}`,
};
} catch (e: any) {
// ignore already declared
if (e.code === 'FST_ERR_SCH_ALREADY_PRESENT') {
return {
$ref: `${id}`,
};
} else {
this._logger.error('Could not register schema', e, schema);
}
return null;
}
};
protected registerSystemConfigRoutes(module: T) {
this._addDataRoute.call(
this,
OCPP2_Namespace.SystemConfig,
() => new Promise((resolve) => resolve(module.config)),
HttpMethod.Get,
);
const systemConfigJsonSchema = z.toJSONSchema(systemConfigSchema, {
target: 'openapi-3.0',
});
this._addDataRoute.call(
this,
OCPP2_Namespace.SystemConfig,
async (request: FastifyRequest<{ Body: SystemConfig }>) => {
await ConfigStoreFactory.getInstance().saveConfig(request.body);
module.config = request.body;
},
HttpMethod.Put,
undefined,
undefined,
undefined,
{
...systemConfigJsonSchema,
$id: 'SystemConfigSchema',
},
);
}
// TODO: for performance reasons can these unknown keys be removed directly from schemas?
private removeUnknownKeys = (schema: any): any => {
// Create a deep copy of the schema
const schemaCopy = structuredClone(schema); // Use structuredClone for a true deep copy
const cleanSchema = (obj: any) => {
if (typeof obj !== 'object' || obj === null) return;
// Remove specific unknown keys
for (const unknownKey of ['comment', 'javaType', 'tsEnumNames']) {
if (unknownKey in obj) {
delete obj[unknownKey];
}
}
// Remove `additionalItems` if `items` is not an array
if ('items' in obj && !Array.isArray(obj.items) && 'additionalItems' in obj) {
delete obj.additionalItems;
}
// Remove `additionalProperties` if `type` is not "object"
if ('additionalProperties' in obj && obj.type !== 'object') {
delete obj.additionalProperties;
}
// Recursively process nested objects
for (const key in obj) {
if (typeof obj[key] === 'object') {
cleanSchema(obj[key]);
}
}
};
// Clean the copied schema
cleanSchema(schemaCopy);
return schemaCopy;
};
/**
* Convert a {@link CallAction} to a normed lowercase URL path.
*
* @param {CallAction} input - The {@link CallAction} to convert to a URL path.
* @param {string} prefix - The module name.
* @returns {string} - String representation of URL path.
*/
protected _toMessagePath(input: CallAction, prefix?: string): string {
const endpointPrefix = prefix || '';
const endpointVersion = (this._ocppVersion ? this._ocppVersion : OCPPVersion.OCPP2_0_1).replace(
/^ocpp/,
'',
);
return `/ocpp/${endpointVersion}${!endpointPrefix.startsWith('/') ? '/' : ''}${endpointPrefix}${!endpointPrefix.endsWith('/') ? '/' : ''}${input.charAt(0).toLowerCase() + input.slice(1)}`;
}
/**
* Convert a namespace to a normed lowercase URL path.
*
* @param {OCPP2_Namespace | OCPP1_6_Namespace | Namespace} input - The {@link OCPP2_Namespace} or {@link OCPP1_6_Namespace} or {@link Namespace} to convert to a URL path.
* @param {string} prefix - The module name.
* @returns {string} - String representation of URL path.
*/
protected _toDataPath(
input: OCPP2_Namespace | OCPP1_6_Namespace | Namespace,
prefix?: string,
): string {
const endpointPrefix = prefix || '';
return `/data${!endpointPrefix.startsWith('/') ? '/' : ''}${endpointPrefix}${!endpointPrefix.endsWith('/') ? '/' : ''}${input.charAt(0).toLowerCase() + input.slice(1)}`;
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { IDataEndpointDefinition } from '@interfaces/api/DataEndpointDefinition.js';
import { HttpMethod } from '@interfaces/api/HttpMethods.js';
import { METADATA_DATA_ENDPOINTS } from '@interfaces/api/metadata.js';
import { Namespace, OCPP1_6_Namespace, OCPP2_Namespace } from '@ocpp/persistence/index.js';
/**
* Decorator for use in module API class to expose methods as REST data endpoints.
*
* @param {OCPP2_Namespace} namespace - The namespace value.
* @param {HttpMethod} method - The HTTP method value.
* @param {object} querySchema - The query schema value (optional).
* @param {object} bodySchema - The body schema value (optional).
* @param {object} paramSchema - The param schema value (optional).
* @param {object} headerSchema - The header schema value (optional).
* @param {object} responseSchema - The response schema value (optional).
* @param {object} tags - The tags value (optional).
* @param {object} security - The security value (optional).
* @param {string} description - The description (optional).
* @return {void} - No return value.
*/
export const AsDataEndpoint = function (
namespace: OCPP2_Namespace | OCPP1_6_Namespace | Namespace,
method: HttpMethod,
querySchema?: object,
bodySchema?: object,
paramSchema?: object,
headerSchema?: object,
responseSchema?: object,
tags?: string | string[],
security?: object[],
description?: string,
) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor): void => {
if (!Reflect.hasMetadata(METADATA_DATA_ENDPOINTS, target.constructor)) {
Reflect.defineMetadata(METADATA_DATA_ENDPOINTS, [], target.constructor);
}
const dataEndpoints = Reflect.getMetadata(
METADATA_DATA_ENDPOINTS,
target.constructor,
) as Array<IDataEndpointDefinition>;
let tagList: string[] | undefined = undefined;
if (tags) {
tagList = Array.isArray(tags) ? tags : [tags];
}
dataEndpoints.push({
method: descriptor.value,
methodName: propertyKey,
namespace: namespace,
httpMethod: method,
querySchema: querySchema,
bodySchema: bodySchema,
paramSchema: paramSchema,
headerSchema: headerSchema,
responseSchema: responseSchema,
tags: tagList,
description: description,
security: security,
});
Reflect.defineMetadata(METADATA_DATA_ENDPOINTS, dataEndpoints, target.constructor);
};
};

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { IMessageEndpointDefinition } from '@interfaces/api/MessageEndpointDefinition.js';
import { METADATA_MESSAGE_ENDPOINTS } from '@interfaces/api/metadata.js';
import type { CallAction } from '@ocpp/rpc/message.js';
/**
* Decorator for use in module API class to expose methods as REST OCPP message endpoints.
*
* @param {CallAction} action - The call action.
* @param {object} bodySchema - The body schema.
* @param {Record<string, any>} optionalQuerystrings - The optional querystrings.
* @return {void} This function does not return anything.
*/
export const AsMessageEndpoint = function (
action: CallAction,
bodySchema: object | ((instance: any) => object),
optionalQuerystrings?: Record<string, any>,
) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor): void => {
if (!Reflect.hasMetadata(METADATA_MESSAGE_ENDPOINTS, target.constructor)) {
Reflect.defineMetadata(
METADATA_MESSAGE_ENDPOINTS,
new Array<IMessageEndpointDefinition>(),
target.constructor,
);
}
const messageEndpoints = Reflect.getMetadata(
METADATA_MESSAGE_ENDPOINTS,
target.constructor,
) as Array<IMessageEndpointDefinition>;
messageEndpoints.push({
action: action,
method: descriptor.value,
methodName: propertyKey,
bodySchema: bodySchema,
optionalQuerystrings: optionalQuerystrings,
});
Reflect.defineMetadata(METADATA_MESSAGE_ENDPOINTS, messageEndpoints, target.constructor);
};
};

View File

@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export const AuthorizationSecurity = {
authorization: [],
};
export const AuthorizationSecurityList = [AuthorizationSecurity];

View File

@@ -0,0 +1,26 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { HttpMethod } from '@interfaces/api/HttpMethods.js';
import { Namespace } from '@ocpp/persistence/namespace.js';
import { OCPP1_6_Namespace, OCPP2_Namespace } from '@ocpp/persistence/index.js';
/**
* Interface for usage in {@link AsDataEndpoint} decorator.
*/
export interface IDataEndpointDefinition {
method: (...args: any[]) => any;
methodName: string;
namespace: OCPP2_Namespace | OCPP1_6_Namespace | Namespace;
httpMethod: HttpMethod;
querySchema?: object;
bodySchema?: object;
paramSchema?: object;
headerSchema?: object;
responseSchema?: object;
description?: string;
tags?: string[];
summary?: string;
security?: object[];
}

View File

@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
export interface ExceptionHandler {
handle(error: FastifyError, request: FastifyRequest, reply: FastifyReply): void;
}

View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export enum HttpHeader {
ContentType = 'Content-Type',
ContentLength = 'Content-Length',
Accept = 'Accept',
Authorization = 'Authorization',
CacheControl = 'Cache-Control',
UserAgent = 'User-Agent',
AcceptEncoding = 'Accept-Encoding',
AcceptLanguage = 'Accept-Language',
Connection = 'Connection',
Host = 'Host',
Referer = 'Referer',
Origin = 'Origin',
AccessControlAllowOrigin = 'Access-Control-Allow-Origin',
ETag = 'ETag',
IfNoneMatch = 'If-None-Match',
}

View File

@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export enum HttpMethod {
Get = 'GET',
Post = 'POST',
Put = 'PUT',
Delete = 'DELETE',
Patch = 'PATCH',
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export enum HttpStatus {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
AMBIGUOUS = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
I_AM_A_TEAPOT = 418,
UNPROCESSABLE_ENTITY = 422,
TOO_MANY_REQUESTS = 429,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
}

View File

@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { CallAction } from '@ocpp/rpc/message.js';
/**
* Interface for usage in {@link AsMessageEndpoint} decorator.
*/
export interface IMessageEndpointDefinition {
action: CallAction;
method: (...args: any[]) => any;
methodName: string;
bodySchema: object | ((instance: any) => object);
optionalQuerystrings?: Record<string, any>;
}

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { DEFAULT_TENANT_ID } from '@config/defineConfig.js';
/**
* The message querystring interface, used for every OCPP message endpoint to validate query parameters.
*/
export interface IMessageQuerystring {
identifier: string | string[];
tenantId?: number;
callbackUrl?: string;
}
/**
* This message querystring schema describes the {@link IMessageQuerystring} interface.
*/
export const IMessageQuerystringSchema = {
$id: 'MessageQuerystring',
type: 'object',
properties: {
identifier: {
anyOf: [
{ type: 'string' },
{
type: 'array',
items: { type: 'string' },
},
],
},
tenantId: { type: 'number', default: DEFAULT_TENANT_ID },
callbackUrl: { type: 'string' },
},
required: ['identifier', 'tenantId'],
};

View File

@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
/**
* The module API interface.
*/
export interface IModuleApi {}

View File

@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { UserInfo } from './UserInfo.js';
/**
* Result of authentication process
*/
export class ApiAuthenticationResult {
/**
* Whether authentication was successful
*/
isAuthenticated: boolean = false;
/**
* User information if authentication was successful
*/
user?: UserInfo;
/**
* Error message if authentication failed
*/
error?: string;
/**
* Creates a new successful authentication result
*
* @param user Authenticated user information
* @returns Authentication result
*/
static success(user: UserInfo): ApiAuthenticationResult {
const result = new ApiAuthenticationResult();
result.isAuthenticated = true;
result.user = user;
return result;
}
/**
* Creates a new failed authentication result
*
* @param error Error message
* @returns Authentication result
*/
static failure(error: string): ApiAuthenticationResult {
const result = new ApiAuthenticationResult();
result.isAuthenticated = false;
result.error = error;
return result;
}
}

View File

@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
/**
* Result of authorization process
*/
export class ApiAuthorizationResult {
/**
* Whether authorization was successful
*/
isAuthorized: boolean = false;
/**
* Error message if authorization failed
*/
error?: string;
/**
* Creates a new successful authorization result
*
* @returns Authorization result
*/
static success(): ApiAuthorizationResult {
const result = new ApiAuthorizationResult();
result.isAuthorized = true;
return result;
}
/**
* Creates a new failed authorization result
*
* @param error Error message
* @returns Authorization result
*/
static failure(error: string): ApiAuthorizationResult {
const result = new ApiAuthorizationResult();
result.isAuthorized = false;
result.error = error;
return result;
}
}

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { UserInfo } from './UserInfo.js';
import type { FastifyRequest } from 'fastify';
import { ApiAuthorizationResult } from './ApiAuthorizationResult.js';
import { ApiAuthenticationResult } from './ApiAuthenticationResult.js';
/**
* Interface for authentication providers
*/
export interface IApiAuthProvider {
/**
* Extracts the authentication token from the request
* @param request
*/
extractToken(request: FastifyRequest): Promise<string | null>;
/**
* Authenticates a token and extracts user information
*
* @param token JWT or other token to authenticate
* @returns Authentication result with user info if successful
*/
authenticateToken(token: string): Promise<ApiAuthenticationResult>;
/**
* Authorizes a user for a specific request
*
* @param user User information
* @param request Fastify request
* @returns Authorization result
*/
authorizeUser(user: UserInfo, request: FastifyRequest): Promise<ApiAuthorizationResult>;
}

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
/**
* Interface for user information extracted from authentication tokens
*/
export interface UserInfo {
/**
* The user ID.
*/
id: string;
/**
* The username.
*/
name: string;
/**
* The user email.
*/
email: string;
/**
* The user roles.
*/
roles: string[];
/**
* Tenant ID associated with the user.
*/
tenantId: string;
/**
* Additional fields associated with the user
*/
[key: string]: any;
}

View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export class UnauthorizedError extends Error {
constructor(message: string) {
super(message);
this.name = 'UnauthorizedError';
}
}

View File

@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export class BadRequestError extends Error {
statusCode = 400;
constructor(message: string) {
super(message);
this.name = 'BadRequestError';
}
}

View File

@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export class NotFoundError extends Error {
statusCode = 404;
constructor(message: string) {
super(message);
this.name = 'NotFoundError';
}
}

View File

@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export class UnauthorizedException extends Error {
constructor(message: string) {
super(message);
this.name = 'UnauthorizedException';
}
}

View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export enum HttpHeader {
ContentType = 'Content-Type',
ContentLength = 'Content-Length',
Accept = 'Accept',
Authorization = 'Authorization',
CacheControl = 'Cache-Control',
UserAgent = 'User-Agent',
AcceptEncoding = 'Accept-Encoding',
AcceptLanguage = 'Accept-Language',
Connection = 'Connection',
Host = 'Host',
Referer = 'Referer',
Origin = 'Origin',
AccessControlAllowOrigin = 'Access-Control-Allow-Origin',
ETag = 'ETag',
IfNoneMatch = 'If-None-Match',
}

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export enum HttpStatus {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
AMBIGUOUS = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
I_AM_A_TEAPOT = 418,
UNPROCESSABLE_ENTITY = 422,
TOO_MANY_REQUESTS = 429,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
}

View File

@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import 'reflect-metadata';
export const METADATA_DATA_ENDPOINTS = 'dataEndpoints';
export const METADATA_MESSAGE_ENDPOINTS = 'messageEndpoints';

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { AuthorizationDto } from '@interfaces/dto/authorization.dto.js';
import type { AuthorizationStatusEnumType } from '@interfaces/dto/types/enums.js';
import type { ConnectorDto } from '@interfaces/dto/connector.dto.js';
import type { EvseDto } from '@interfaces/dto/evse.dto.js';
import type { IMessageContext } from '@interfaces/messages/MessageContext.js';
export interface IAuthorizer {
/**
* Interface for adding additional authorization logic.
* If status is not Accepted, the authorization process will stop and the rejection put into the response.
* The order of Authorizers can lead to different outputs; instantiate them in the order they should be called.
*
* @param {AuthorizationDto} authorization The authorization object associated with the idToken in the request. No modifications should be made to this object.
* @param {IMessageContext} context
*
* @returns {Promise<AuthorizationStatusEnumType>} The updated authorization status
**/
authorize(
authorization: AuthorizationDto,
context: IMessageContext,
evse?: EvseDto,
connector?: ConnectorDto,
): Promise<AuthorizationStatusEnumType>;
}

View File

@@ -0,0 +1,99 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { ClassConstructor } from 'class-transformer';
/**
* Interface for cache
* Implementers SHALL ensure minimal logic outside of promise resolution or async function to prevent lag
* Users of this interface can assume these methods behave asynchronously
*/
export interface ICache {
exists(key: string, namespace?: string): Promise<boolean>;
/**
* Returns true if any keys exist in the given namespace.
*
* @param {string} namespace - The namespace to check.
* @returns {Promise<boolean>} - Returns true if at least one key exists in the namespace.
* */
existsAnyInNamespace(namespace: string): Promise<boolean>;
remove<T>(key: string, namespace?: string): Promise<T | null>;
/**
* Monitors a key for potential changes to its value.
* If key-value does not exist this method will wait for it to exist or return null at the end of the wait period.
* If value is removed, the method will return null.
*
* @param {string} key - The key for the value.
* @param {number} [waitSeconds] - The number of seconds after which the method should return if the value has not been modified by then.
* @param {string} [namespace] - The namespace for the key.
* @returns {Promise<string | null>} Returns the value as string once it is modified or waitSeconds has elapsed; or null if the key does not exist.
* */
onChange<T>(
key: string,
waitSeconds: number,
namespace?: string,
classConstructor?: () => ClassConstructor<T>,
): Promise<T | null>;
/**
* Gets a value asynchronously from the underlying cache.
*
* @param {string} key - The key for the value.
* @param {string} [namespace] - The namespace for the key.
* @returns {Promise<string | null>} - Returns the value as string or null if the key does not exist.
* */
get<T>(
key: string,
namespace?: string,
classConstructor?: () => ClassConstructor<T>,
): Promise<T | null>;
/**
* Sets a value asynchronously in the underlying cache.
*
* @param {string} key - The key for the value.
* @param {string} value - The value to set.
* @param {string} [namespace] - The namespace for the key.
* @param {number} [expireSeconds] - The number of seconds after which the key should expire.
* @returns {Promise<boolean>} - Returns true if the value was set successfully.
* */
set(key: string, value: string, namespace?: string, expireSeconds?: number): Promise<boolean>;
/**
* Sets a value asynchronously in the underlying cache if it doesn't exist. Returns false if the key already exists.
*
* @param {string} key - The key for the value.
* @param {string} value - The value to set.
* @param {string} [namespace] - The namespace for the key.
* @param {number} [expireSeconds] - The number of seconds after which the key should expire.
* @returns {Promise<boolean>} - Returns true if the value was set successfully.
* */
setIfNotExist(
key: string,
value: string,
namespace?: string,
expireSeconds?: number,
): Promise<boolean>;
/**
* Updates the expiration of a key without modifying its value. Returns false if the key does not exist.
*
* @param {string} key - The key to update.
* @param {number} expireSeconds - The number of seconds from now after which the key should expire.
* @param {string} [namespace] - The namespace for the key.
* @returns {Promise<boolean>} - Returns true if the expiration was updated successfully, false if the key does not exist.
* */
updateExpiration(key: string, expireSeconds: number, namespace?: string): Promise<boolean>;
/**
* Pings the cache to check if it is responsive, for health checks. Implementers should ensure this method is lightweight and does not cause significant delay.
*
* @returns {Promise<void>} - Resolves if the cache is responsive, rejects if it is not.
*/
ping(): Promise<void>;
}

View File

@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { DEFAULT_TENANT_ID } from '@config/defineConfig.js';
/**
* Cache namespace, used for grouping cache entries
*/
export enum CacheNamespace {
CentralSystem = 'csms',
ChargingStation = 'cs',
TenantPathMapping = 'tpm',
Transactions = 'tx',
Connections = 'conn',
Protocol = 'prtcl',
Other = 'other',
}
export const PATH_DELIMITER = ':';
export const getCacheTenantPathMappingKey = (serverId: string, path: string): string => {
return serverId + PATH_DELIMITER + path;
};
/*
* Helper methods to create a unique identifier used in the cache and queues.
* This is usually a combination between the tenantId and the ocppConnectionName.
*/
export const IDENTIFIER_DELIMITER = ':';
export const createIdentifier = (tenantId: number, ...args: any[]): string =>
[tenantId, ...(args ?? [])].join(IDENTIFIER_DELIMITER);
export const getTenantIdFromIdentifier = (identifier: string): number => {
const identifierSplit = identifier.split(IDENTIFIER_DELIMITER);
return identifierSplit?.[0] ? Number(identifierSplit?.[0]) : DEFAULT_TENANT_ID;
};
export const getStationIdFromIdentifier = (identifier: string): string => {
const identifierSplit = identifier.split(IDENTIFIER_DELIMITER);
return identifierSplit?.[1] ?? identifier;
};
/**
* Used in the Connections Namespace as the value, to represent a websocket connection
* Is stringified from JSON when stored in the cache
*/
export interface IWebsocketConnection {
id: string;
/**
* Stored as ISO string in the cache, converted to Date when retrieved
*/
timeConnected: string;
protocol: string;
allowUnknownChargingStations: boolean;
}

View File

@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { TenantPartnerSchema } from './tenant.partner.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { AsyncJobNameSchema } from './types/enums.js';
export const PaginatedParamsSchema = z.object({
offset: z.number().int().optional(),
limit: z.number().int().optional(),
dateFrom: z.date().optional(),
dateTo: z.date().optional(),
});
export type PaginatedParams = z.infer<typeof PaginatedParamsSchema>;
export const AsyncJobSchema = BaseSchema.extend({
jobId: z.string().uuid(),
jobName: AsyncJobNameSchema,
tenantPartnerId: z.number().int(),
tenantPartner: TenantPartnerSchema.optional(),
finishedAt: z.date().optional(),
stoppedAt: z.date().nullable().optional(),
stopScheduled: z.boolean().default(false),
isFailed: z.boolean().default(false),
paginatedParams: PaginatedParamsSchema,
totalObjects: z.number().int().optional(),
});
export const AsyncJobProps = AsyncJobSchema.keyof().enum;
export type AsyncJobDto = z.infer<typeof AsyncJobSchema>;
export const AsyncJobCreateSchema = AsyncJobSchema.omit({
jobId: true,
tenant: true,
tenantPartner: true,
updatedAt: true,
createdAt: true,
});
export type AsyncJobCreate = z.infer<typeof AsyncJobCreateSchema>;
export const AsyncJobRequestSchema = z.object({
tenantPartnerId: z.number().int(),
paginatedParams: PaginatedParamsSchema,
});
export type AsyncJobRequest = z.infer<typeof AsyncJobRequestSchema>;
export const asyncJobSchemas = {
AsyncJob: AsyncJobSchema,
AsyncJobCreate: AsyncJobCreateSchema,
AsyncJobRequest: AsyncJobRequestSchema,
};
const asyncJob: AsyncJobDto = {
tenantId: 1,
jobId: 'some-uuid',
jobName: 'FETCH_OCPI_TOKENS',
tenantPartnerId: 1,
finishedAt: new Date(),
stoppedAt: null,
stopScheduled: false,
isFailed: false,
paginatedParams: {
offset: 0,
limit: 10,
},
totalObjects: 100,
};
asyncJob.jobId = 'another-uuid';

View File

@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod/v4';
import { TenantPartnerSchema } from './tenant.partner.dto.js';
import { AdditionalInfoSchema, RealTimeAuthLastAttemptSchema } from './types/authorization.js';
import { BaseSchema } from './types/base.dto.js';
import {
AuthorizationStatusEnumSchema,
AuthorizationWhitelistEnumSchema,
IdTokenEnumSchema,
} from './types/enums.js';
const authorizationFields = {
id: z.number().int().optional(),
allowedConnectorTypes: z.array(z.string()).optional(),
disallowedEvseIdPrefixes: z.array(z.string()).optional(),
idToken: z.string(),
idTokenType: IdTokenEnumSchema.nullable().optional(),
additionalInfo: z.tuple([AdditionalInfoSchema]).rest(AdditionalInfoSchema).nullable().optional(),
status: AuthorizationStatusEnumSchema,
cacheExpiryDateTime: z.iso.datetime().nullable().optional(),
chargingPriority: z.number().int().nullable().optional(),
language1: z.string().nullable().optional(),
language2: z.string().nullable().optional(),
personalMessage: z.any().nullable().optional(),
customData: z.any().nullable().optional(),
concurrentTransaction: z.boolean().optional(),
isPrepaid: z.boolean().optional(),
prepaidBalance: z.number().nullable().optional(),
realTimeAuth: AuthorizationWhitelistEnumSchema.nullable().optional(),
realTimeAuthLastAttempt: RealTimeAuthLastAttemptSchema.nullable().optional(),
realTimeAuthTimeout: z.number().int().nullable().optional(),
realTimeAuthUrl: z.string().optional(),
tenantPartnerId: z.number().int().nullable().optional(),
tenantPartner: TenantPartnerSchema.nullable().optional(),
groupAuthorizationId: z.number().int().nullable().optional(),
tariffId: z.number().int().nullable().optional(),
};
export const GroupAuthorizationSchema = BaseSchema.extend(authorizationFields);
export type GroupAuthorizationDto = z.infer<typeof GroupAuthorizationSchema>;
export const AuthorizationSchema = BaseSchema.extend({
...authorizationFields,
groupAuthorizationId: z.number().int().nullable().optional(),
groupAuthorization: z.lazy(() => GroupAuthorizationSchema).optional(),
});
export const AuthorizationProps = AuthorizationSchema.keyof().enum;
export type AuthorizationDto = z.infer<typeof AuthorizationSchema>;
export const AuthorizationCreateSchema = AuthorizationSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
groupAuthorization: true,
tenantPartner: true,
});
export type AuthorizationCreate = z.infer<typeof AuthorizationCreateSchema>;
export const AuthorizationUpdateSchema = AuthorizationSchema.partial()
.omit({
tenant: true,
updatedAt: true,
createdAt: true,
groupAuthorization: true,
tenantPartner: true,
})
.required({ id: true, tenantId: true });
export type AuthorizationUpdate = z.infer<typeof AuthorizationUpdateSchema>;
export const authorizationSchemas = {
Authorization: AuthorizationSchema,
AuthorizationCreate: AuthorizationCreateSchema,
AuthorizationUpdate: AuthorizationUpdateSchema,
};

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import { VariableAttributeSchema } from './variable.attribute.dto.js';
export const BootSchema = BaseSchema.extend({
id: z.string(),
lastBootTime: z.iso.datetime().nullable().optional(),
heartbeatInterval: z.number().int().nullable().optional(),
bootRetryInterval: z.number().int().nullable().optional(),
status: z.any(),
statusInfo: z.record(z.string(), z.any()).nullable().optional(), // JSONB
getBaseReportOnPending: z.boolean().nullable().optional(),
pendingBootSetVariables: z.array(VariableAttributeSchema).optional(),
variablesRejectedOnLastBoot: z.array(z.record(z.string(), z.any())).nullable().optional(),
bootWithRejectedVariables: z.boolean().nullable().optional(),
changeConfigurationsOnPending: z.boolean().nullable().optional(),
getConfigurationsOnPending: z.boolean().nullable().optional(),
});
export const BootProps = BootSchema.keyof().enum;
export type BootDto = z.infer<typeof BootSchema>;
export const BootCreateSchema = BootSchema.omit({
tenant: true,
updatedAt: true,
createdAt: true,
pendingBootSetVariables: true,
});
export type BootCreate = z.infer<typeof BootCreateSchema>;
export const BootUpdateSchema = BootSchema.partial()
.omit({
tenant: true,
updatedAt: true,
createdAt: true,
pendingBootSetVariables: true,
})
.required({ id: true, tenantId: true });
export type BootUpdate = z.infer<typeof BootUpdateSchema>;
export const bootSchemas = {
Boot: BootSchema,
BootCreate: BootCreateSchema,
BootUpdate: BootUpdateSchema,
};

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const SignatureAlgorithmSchema = z.enum(['SHA256withRSA', 'SHA256withECDSA']);
export const CountryNameSchema = z.enum(['US']);
export type SignatureAlgorithm = z.infer<typeof SignatureAlgorithmSchema>;
export type CountryName = z.infer<typeof CountryNameSchema>;
export const CertificateSchema = BaseSchema.extend({
id: z.number().int().optional(),
serialNumber: z.number().int(), // BIGINT in DB
issuerName: z.string(),
organizationName: z.string(),
commonName: z.string(),
keyLength: z.number().int().nullable().optional(),
validBefore: z.iso.datetime().nullable().optional(),
signatureAlgorithm: SignatureAlgorithmSchema.nullable().optional(),
countryName: CountryNameSchema.nullable().optional(),
isCA: z.boolean().optional(),
pathLen: z.number().int().nullable().optional(),
certificateFileId: z.string().nullable().optional(),
certificateFileHash: z.string().nullable().optional(),
privateKeyFileId: z.string().nullable().optional(),
signedBy: z.number().int().nullable().optional(),
});
export const CertificateProps = CertificateSchema.keyof().enum;
export type CertificateDto = z.infer<typeof CertificateSchema>;
export const CertificateCreateSchema = CertificateSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type CertificateCreate = z.infer<typeof CertificateCreateSchema>;
export const certificateSchemas = {
Certificate: CertificateSchema,
CertificateCreate: CertificateCreateSchema,
};

View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const ChangeConfigurationSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
key: z.string(),
value: z.string().nullable().optional(),
readonly: z.boolean().nullable().optional(),
});
export const ChangeConfigurationProps = ChangeConfigurationSchema.keyof().enum;
export type ChangeConfigurationDto = z.infer<typeof ChangeConfigurationSchema>;
export const ChangeConfigurationCreateSchema = ChangeConfigurationSchema.omit({
tenant: true,
updatedAt: true,
createdAt: true,
});
export type ChangeConfigurationCreate = z.infer<typeof ChangeConfigurationCreateSchema>;
export const changeConfigurationSchemas = {
ChangeConfiguration: ChangeConfigurationSchema,
ChangeConfigurationCreate: ChangeConfigurationCreateSchema,
};

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import {
ACChargingParametersSchema,
DCChargingParametersSchema,
} from './types/charging.parameters.js';
import { EnergyTransferModeEnumSchema } from './types/enums.js';
export const ChargingNeedsSchema = BaseSchema.extend({
id: z.number().int().optional(),
acChargingParameters: ACChargingParametersSchema.nullable().optional(),
dcChargingParameters: DCChargingParametersSchema.nullable().optional(),
departureTime: z.iso.datetime().nullable().optional(),
requestedEnergyTransfer: EnergyTransferModeEnumSchema,
maxScheduleTuples: z.number().int().nullable().optional(),
evseId: z.number().int(),
transactionDatabaseId: z.number().int(),
});
export const ChargingNeedsProps = ChargingNeedsSchema.keyof().enum;
export type ChargingNeedsDto = z.infer<typeof ChargingNeedsSchema>;
export const ChargingNeedsCreateSchema = ChargingNeedsSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type ChargingNeedsCreate = z.infer<typeof ChargingNeedsCreateSchema>;
export const chargingNeedsSchemas = {
ChargingNeeds: ChargingNeedsSchema,
ChargingNeedsCreate: ChargingNeedsCreateSchema,
};

View File

@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ChargingScheduleSchema } from './charging.schedule.dto.js';
import { BaseSchema } from './types/base.dto.js';
import {
ChargingLimitSourceEnumSchema,
ChargingProfileKindEnumSchema,
ChargingProfilePurposeEnumSchema,
RecurrencyKindEnumSchema,
} from './types/enums.js';
export const ChargingProfileSchema = BaseSchema.extend({
databaseId: z.number().int(),
ocppConnectionName: z.string(),
id: z.number().int().optional(),
chargingProfileKind: ChargingProfileKindEnumSchema,
chargingProfilePurpose: ChargingProfilePurposeEnumSchema,
recurrencyKind: RecurrencyKindEnumSchema.nullable().optional(),
stackLevel: z.number().int(),
validFrom: z.iso.datetime().nullable().optional(),
validTo: z.iso.datetime().nullable().optional(),
evseId: z.number().int().nullable().optional(),
isActive: z.boolean().default(false),
chargingLimitSource: ChargingLimitSourceEnumSchema.default('CSO').nullable().optional(),
chargingSchedule: z.union([
z.tuple([ChargingScheduleSchema]),
z.tuple([ChargingScheduleSchema, ChargingScheduleSchema]),
z.tuple([ChargingScheduleSchema, ChargingScheduleSchema, ChargingScheduleSchema]),
]),
transactionDatabaseId: z.number().int().nullable().optional(),
});
export const ChargingProfileProps = ChargingProfileSchema.keyof().enum;
export type ChargingProfileDto = z.infer<typeof ChargingProfileSchema>;
export const ChargingProfileCreateSchema = ChargingProfileSchema.omit({
databaseId: true,
tenant: true,
chargingSchedule: true,
transaction: true,
updatedAt: true,
createdAt: true,
});
export type ChargingProfileCreate = z.infer<typeof ChargingProfileCreateSchema>;
export const chargingProfileSchemas = {
ChargingProfile: ChargingProfileSchema,
ChargingProfileCreate: ChargingProfileCreateSchema,
};

View File

@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { SalesTariffSchema } from './sales.tariff.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { ChargingRateUnitEnumSchema } from './types/enums.js';
export const ChargingScheduleSchema = BaseSchema.extend({
databaseId: z.number().int(),
id: z.number().int(),
ocppConnectionName: z.string(),
chargingRateUnit: ChargingRateUnitEnumSchema,
chargingSchedulePeriod: z.tuple([z.any()]).rest(z.any()), // Non-empty array of JSONB
duration: z.number().int().nullable().optional(),
minChargingRate: z.number().nullable().optional(), // DECIMAL
startSchedule: z.string().nullable().optional(),
timeBase: z.iso.datetime().optional(),
chargingProfileDatabaseId: z.number().int().optional(),
salesTariff: SalesTariffSchema.optional(),
});
export const ChargingScheduleProps = ChargingScheduleSchema.keyof().enum;
export type ChargingScheduleDto = z.infer<typeof ChargingScheduleSchema>;
export const ChargingScheduleCreateSchema = ChargingScheduleSchema.omit({
databaseId: true,
tenant: true,
chargingProfile: true,
salesTariff: true,
updatedAt: true,
createdAt: true,
});
export type ChargingScheduleCreate = z.infer<typeof ChargingScheduleCreateSchema>;
export const chargingScheduleSchemas = {
ChargingSchedule: ChargingScheduleSchema,
ChargingScheduleCreate: ChargingScheduleCreateSchema,
};

View File

@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ConnectorSchema } from './connector.dto.js';
import { EvseSchema } from './evse.dto.js';
import { BaseSchema } from './types/base.dto.js';
import {
ChargingStationCapabilitySchema,
ChargingStationParkingRestrictionSchema,
} from './types/enums.js';
import { PointSchema } from './types/location.js';
import { OCPPVersionSchema } from './types/ocpp.message.js';
export const ChargingStationSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string().max(36),
isOnline: z.boolean(),
protocol: OCPPVersionSchema.nullable().optional(),
latestOcppMessageTimestamp: z.string().datetime().nullable().optional(),
chargePointVendor: z.string().max(20).nullable().optional(),
chargePointModel: z.string().max(20).nullable().optional(),
chargePointSerialNumber: z.string().max(25).nullable().optional(),
chargeBoxSerialNumber: z.string().max(25).nullable().optional(),
firmwareVersion: z.string().max(50).nullable().optional(),
iccid: z.string().max(20).nullable().optional(),
imsi: z.string().max(20).nullable().optional(),
meterType: z.string().max(25).nullable().optional(),
meterSerialNumber: z.string().max(25).nullable().optional(),
coordinates: PointSchema.nullable().optional(),
floorLevel: z.string().nullable().optional(),
parkingRestrictions: z.array(ChargingStationParkingRestrictionSchema).nullable().optional(),
capabilities: z.array(ChargingStationCapabilitySchema).nullable().optional(),
use16StatusNotification0: z.boolean().default(true).nullable().optional(),
locationId: z.number().int().nullable().optional(),
networkProfiles: z.any().optional(),
evses: z.array(EvseSchema).nullable().optional(),
connectors: z.array(ConnectorSchema).nullable().optional(),
});
export const ChargingStationProps = ChargingStationSchema.keyof().enum;
export type ChargingStationDto = z.infer<typeof ChargingStationSchema>;
export const ChargingStationCreateSchema = ChargingStationSchema.omit({
tenant: true,
statusNotifications: true,
transactions: true,
location: true,
networkProfiles: true,
evses: true,
connectors: true,
updatedAt: true,
createdAt: true,
});
export type ChargingStationCreate = z.infer<typeof ChargingStationCreateSchema>;
// OCPI-specific validation (requires evses and connectors)
export const ChargingStationOCPISchema = ChargingStationSchema.extend({
evses: z.array(EvseSchema).min(1, 'OCPI requires at least one EVSE'),
connectors: z.array(ConnectorSchema).min(1, 'OCPI requires at least one connector'),
coordinates: PointSchema, // Required for OCPI (not nullable)
});
export type ChargingStationOCPI = z.infer<typeof ChargingStationOCPISchema>;
export const chargingStationSchemas = {
ChargingStation: ChargingStationSchema,
ChargingStationCreate: ChargingStationCreateSchema,
ChargingStationOCPI: ChargingStationOCPISchema,
};

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ServerNetworkProfileSchema } from './server.network.profile.dto.js';
import { SetNetworkProfileSchema } from './set.network.profile.dto.js';
import { BaseSchema } from './types/base.dto.js';
export const ChargingStationNetworkProfileSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
configurationSlot: z.number().int(),
setNetworkProfileId: z.number().int(),
setNetworkProfile: SetNetworkProfileSchema,
websocketServerConfigId: z.string().optional(),
websocketServerConfig: ServerNetworkProfileSchema.optional(),
});
export const ChargingStationNetworkProfileProps = ChargingStationNetworkProfileSchema.keyof().enum;
export type ChargingStationNetworkProfileDto = z.infer<typeof ChargingStationNetworkProfileSchema>;
export const ChargingStationNetworkProfileCreateSchema = ChargingStationNetworkProfileSchema.omit({
id: true,
tenant: true,
setNetworkProfile: true,
websocketServerConfig: true,
updatedAt: true,
createdAt: true,
});
export type ChargingStationNetworkProfileCreate = z.infer<
typeof ChargingStationNetworkProfileCreateSchema
>;
export const chargingStationNetworkProfileSchemas = {
ChargingStationNetworkProfile: ChargingStationNetworkProfileSchema,
ChargingStationNetworkProfileCreate: ChargingStationNetworkProfileCreateSchema,
};

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const ChargingStationSecurityInfoSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
publicKeyFileId: z.string(),
});
export const ChargingStationSecurityInfoProps = ChargingStationSecurityInfoSchema.keyof().enum;
export type ChargingStationSecurityInfoDto = z.infer<typeof ChargingStationSecurityInfoSchema>;
export const ChargingStationSecurityInfoCreateSchema = ChargingStationSecurityInfoSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type ChargingStationSecurityInfoCreate = z.infer<
typeof ChargingStationSecurityInfoCreateSchema
>;
export const chargingStationSecurityInfoSchemas = {
ChargingStationSecurityInfo: ChargingStationSecurityInfoSchema,
ChargingStationSecurityInfoCreate: ChargingStationSecurityInfoCreateSchema,
};

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ChargingStationSchema } from './charging.station.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { ChargingStationSequenceTypeSchema } from './types/enums.js';
export const ChargingStationSequenceSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string().max(36),
type: ChargingStationSequenceTypeSchema,
value: z.number().int().default(0), // BIGINT
station: ChargingStationSchema.optional(),
});
export const ChargingStationSequenceProps = ChargingStationSequenceSchema.keyof().enum;
export type ChargingStationSequenceDto = z.infer<typeof ChargingStationSequenceSchema>;
export const ChargingStationSequenceCreateSchema = ChargingStationSequenceSchema.omit({
id: true,
tenant: true,
station: true,
updatedAt: true,
createdAt: true,
});
export type ChargingStationSequenceCreate = z.infer<typeof ChargingStationSequenceCreateSchema>;
export const chargingStationSequenceSchemas = {
ChargingStationSequence: ChargingStationSequenceSchema,
ChargingStationSequenceCreate: ChargingStationSequenceCreateSchema,
};

View File

@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { EvseTypeSchema } from './evse.type.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { VariableSchema } from './variable.dto.js';
export const ComponentSchema = BaseSchema.extend({
id: z.number().int().optional(),
name: z.string(),
instance: z.string().nullable().optional(),
evse: EvseTypeSchema.optional(),
evseDatabaseId: z.number().int().nullable().optional(),
variables: z.array(VariableSchema).optional(),
customData: z.any().nullable().optional(),
});
export const ComponentProps = ComponentSchema.keyof().enum;
export type ComponentDto = z.infer<typeof ComponentSchema>;
export const ComponentCreateSchema = ComponentSchema.omit({
id: true,
tenant: true,
evse: true,
variables: true,
updatedAt: true,
createdAt: true,
});
export type ComponentCreate = z.infer<typeof ComponentCreateSchema>;
export const componentSchemas = {
Component: ComponentSchema,
ComponentCreate: ComponentCreateSchema,
};

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const CompositeScheduleSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
evseId: z.number().int(),
duration: z.number().int(),
scheduleStart: z.iso.datetime(),
chargingRateUnit: z.string(),
chargingSchedulePeriod: z.tuple([z.any()]).rest(z.any()),
});
export const CompositeScheduleProps = CompositeScheduleSchema.keyof().enum;
export type CompositeScheduleDto = z.infer<typeof CompositeScheduleSchema>;
export const CompositeScheduleCreateSchema = CompositeScheduleSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type CompositeScheduleCreate = z.infer<typeof CompositeScheduleCreateSchema>;
export const compositeScheduleSchemas = {
CompositeSchedule: CompositeScheduleSchema,
CompositeScheduleCreate: CompositeScheduleCreateSchema,
};

View File

@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { TariffSchema } from './tariff.dto.js';
import { BaseSchema } from './types/base.dto.js';
import {
ConnectorErrorCodeEnumSchema,
ConnectorFormatEnumSchema,
ConnectorPowerTypeEnumSchema,
ConnectorStatusEnumSchema,
ConnectorTypeEnumSchema,
} from './types/enums.js';
export const ConnectorSchemaWithoutParent = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
evseId: z.number().int(),
connectorId: z.number().int(),
evseTypeConnectorId: z.number().int().optional(),
status: ConnectorStatusEnumSchema.default('Unknown').nullable().optional(),
type: ConnectorTypeEnumSchema.nullable().optional(),
format: ConnectorFormatEnumSchema.nullable().optional(),
errorCode: ConnectorErrorCodeEnumSchema.default('NoError').nullable().optional(),
powerType: ConnectorPowerTypeEnumSchema.nullable().optional(),
maximumAmperage: z.number().int().nullable().optional(),
maximumVoltage: z.number().int().nullable().optional(),
maximumPowerWatts: z.number().int().nullable().optional(),
timestamp: z.iso.datetime(),
info: z.string().nullable().optional(),
vendorId: z.string().nullable().optional(),
vendorErrorCode: z.string().nullable().optional(),
termsAndConditionsUrl: z.string().nullable().optional(),
tariff: TariffSchema.nullable().optional(),
});
export const ConnectorSchema = ConnectorSchemaWithoutParent.extend({
tenant: z.any().optional(),
evse: z.any().optional(),
chargingStation: z.any().optional(),
});
export const ConnectorProps = ConnectorSchema.keyof().enum;
export type ConnectorDto = z.infer<typeof ConnectorSchema>;
export const ConnectorCreateSchema = ConnectorSchema.omit({
id: true,
tenant: true,
evse: true,
chargingStation: true,
tariff: true,
updatedAt: true,
createdAt: true,
});
export type ConnectorCreate = z.infer<typeof ConnectorCreateSchema>;
export const connectorSchemas = {
Connector: ConnectorSchema,
ConnectorCreate: ConnectorCreateSchema,
};

View File

@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ComponentSchema } from './component.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { VariableSchema } from './variable.dto.js';
import { EventNotificationEnumSchema, EventTriggerEnumSchema } from './types/enums.js';
export const EventDataSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
eventId: z.number().int(),
trigger: EventTriggerEnumSchema,
cause: z.number().int().nullable().optional(),
timestamp: z.iso.datetime(),
actualValue: z.string(),
techCode: z.string().nullable().optional(),
techInfo: z.string().nullable().optional(),
cleared: z.boolean().nullable().optional(),
transactionId: z.string().nullable().optional(),
variableMonitoringId: z.number().int().nullable().optional(),
eventNotificationType: EventNotificationEnumSchema,
variable: VariableSchema,
variableId: z.number().int().optional(),
component: ComponentSchema,
componentId: z.number().int().optional(),
});
export const EventDataProps = EventDataSchema.keyof().enum;
export type EventDataDto = z.infer<typeof EventDataSchema>;
export const EventDataCreateSchema = EventDataSchema.omit({
id: true,
tenant: true,
variable: true,
component: true,
updatedAt: true,
createdAt: true,
});
export type EventDataCreate = z.infer<typeof EventDataCreateSchema>;
export const eventDataSchemas = {
EventData: EventDataSchema,
EventDataCreate: EventDataCreateSchema,
};

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ConnectorSchemaWithoutParent } from './connector.dto.js';
import { BaseSchema } from './types/base.dto.js';
export const EvseSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
evseTypeId: z.number().int().optional(),
evseId: z.string(), // eMI3 compliant EVSE ID
physicalReference: z.string().nullable().optional(),
removed: z.boolean().optional(),
connectors: z.array(ConnectorSchemaWithoutParent).nullable().optional(),
});
export const EvseProps = EvseSchema.keyof().enum;
export type EvseDto = z.infer<typeof EvseSchema>;
export const EvseCreateSchema = EvseSchema.omit({
id: true,
tenant: true,
chargingStation: true,
connectors: true,
updatedAt: true,
createdAt: true,
});
export type EvseCreate = z.infer<typeof EvseCreateSchema>;
export const evseSchemas = {
Evse: EvseSchema,
EvseCreate: EvseCreateSchema,
};

View File

@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const EvseTypeSchema = BaseSchema.extend({
databaseId: z.number().int().optional(),
id: z.number().int(),
connectorId: z.number().int().nullable().optional(),
});
export const EvseTypeProps = EvseTypeSchema.keyof().enum;
export type EvseTypeDto = z.infer<typeof EvseTypeSchema>;
export const EvseTypeCreateSchema = EvseTypeSchema.omit({
databaseId: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type EvseTypeCreate = z.infer<typeof EvseTypeCreateSchema>;
export const evseTypeSchemas = {
EvseType: EvseTypeSchema,
EvseTypeCreate: EvseTypeCreateSchema,
};

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import { CertificateUseEnumSchema, HashAlgorithmEnumSchema } from './types/enums.js';
export const InstalledCertificateSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string().max(36),
hashAlgorithm: HashAlgorithmEnumSchema,
issuerNameHash: z.string().nullable().optional(),
issuerKeyHash: z.string().nullable().optional(),
serialNumber: z.string().nullable().optional(),
certificateType: CertificateUseEnumSchema,
});
export const InstalledCertificateProps = InstalledCertificateSchema.keyof().enum;
export type InstalledCertificateDto = z.infer<typeof InstalledCertificateSchema>;
export const InstalledCertificateCreateSchema = InstalledCertificateSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type InstalledCertificateCreate = z.infer<typeof InstalledCertificateCreateSchema>;
export const installedCertificateSchemas = {
InstalledCertificate: InstalledCertificateSchema,
InstalledCertificateCreate: InstalledCertificateCreateSchema,
};

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ChargingStationSchema } from './charging.station.dto.js';
import { StatusNotificationSchema } from './status.notification.dto.js';
import { BaseSchema } from './types/base.dto.js';
export const LatestStatusNotificationSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
statusNotificationId: z.string(),
chargingStation: ChargingStationSchema.optional(),
statusNotification: StatusNotificationSchema.optional(),
});
export const LatestStatusNotificationProps = LatestStatusNotificationSchema.keyof().enum;
export type LatestStatusNotificationDto = z.infer<typeof LatestStatusNotificationSchema>;
export const LatestStatusNotificationCreateSchema = LatestStatusNotificationSchema.omit({
id: true,
tenant: true,
chargingStation: true,
statusNotification: true,
updatedAt: true,
createdAt: true,
});
export type LatestStatusNotificationCreate = z.infer<typeof LatestStatusNotificationCreateSchema>;
export const latestStatusNotificationSchemas = {
LatestStatusNotification: LatestStatusNotificationSchema,
LatestStatusNotificationCreate: LatestStatusNotificationCreateSchema,
};

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod/v4';
import { BaseSchema } from './types/base.dto.js';
import { AuthorizationSchema } from './authorization.dto.js';
export const LocalListAuthorizationSchema = BaseSchema.extend({
id: z.number().int().optional(),
allowedConnectorTypes: z.array(z.string()).optional(),
disallowedEvseIdPrefixes: z.array(z.string()).optional(),
idToken: z.string(),
idTokenType: z.string().nullable().optional(),
additionalInfo: z.any().nullable().optional(), // JSONB
status: z.string(),
cacheExpiryDateTime: z.iso.datetime().nullable().optional(),
chargingPriority: z.number().int().nullable().optional(),
language1: z.string().nullable().optional(),
language2: z.string().nullable().optional(),
personalMessage: z.any().nullable().optional(),
groupAuthorizationId: z.number().int().nullable().optional(),
groupAuthorization: z.lazy(() => AuthorizationSchema).optional(),
authorizationId: z.number().int().optional(),
authorization: z.lazy(() => AuthorizationSchema).optional(),
customData: z.any().nullable().optional(),
});
export type LocalListAuthorizationDto = z.infer<typeof LocalListAuthorizationSchema>;
export const LocalListAuthorizationCreateSchema = LocalListAuthorizationSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
groupAuthorization: true,
authorization: true,
sendLocalLists: true,
localListVersions: true,
});
export const localListAuthorizationSchemas = {
LocalListAuthorization: LocalListAuthorizationSchema,
LocalListAuthorizationCreate: LocalListAuthorizationCreateSchema,
};

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod/v4';
import { BaseSchema } from './types/base.dto.js';
import { LocalListAuthorizationSchema } from './local.list.authorization.dto.js';
export const LocalListVersionSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
versionNumber: z.number().int(),
localAuthorizationList: z
.array(z.lazy(() => LocalListAuthorizationSchema))
.nonempty()
.nullable()
.optional(),
customData: z.any().nullable().optional(),
});
export type LocalListVersionDto = z.infer<typeof LocalListVersionSchema>;
export const LocalListVersionCreateSchema = LocalListVersionSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
localAuthorizationList: true,
});

View File

@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ChargingStationSchema } from './charging.station.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { LocationFacilityEnumSchema, LocationParkingEnumSchema } from './types/enums.js';
import { LocationHoursSchema, PointSchema } from './types/location.js';
export const LocationSchema = BaseSchema.extend({
id: z.number().int().optional(),
name: z.string(),
address: z.string(),
city: z.string(),
postalCode: z.string(),
state: z.string(),
country: z.string(),
publishUpstream: z.boolean().default(true),
timeZone: z.string().default('UTC'),
parkingType: LocationParkingEnumSchema.nullable().optional(),
facilities: z.array(LocationFacilityEnumSchema).nullable().optional(),
openingHours: LocationHoursSchema.nullable().optional(),
coordinates: PointSchema,
chargingPool: z.array(ChargingStationSchema).nullable().optional(),
});
export const LocationProps = LocationSchema.keyof().enum;
export type LocationDto = z.infer<typeof LocationSchema>;
export const LocationCreateSchema = LocationSchema.omit({
id: true,
tenant: true,
chargingPool: true,
updatedAt: true,
createdAt: true,
});
export type LocationCreate = z.infer<typeof LocationCreateSchema>;
export const locationSchemas = {
Location: LocationSchema,
LocationCreate: LocationCreateSchema,
};

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ComponentSchema } from './component.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { MessagePriorityEnumSchema } from './types/enums.js';
import { MessageStateEnumSchema } from './types/enums.js';
import { MessageContentSchema } from './types/message.info.js';
export const MessageInfoSchema = BaseSchema.extend({
databaseId: z.number().int(),
ocppConnectionName: z.string(),
id: z.number().int(),
priority: MessagePriorityEnumSchema,
state: MessageStateEnumSchema.nullable().optional(),
startDateTime: z.iso.datetime().nullable().optional(),
endDateTime: z.iso.datetime().nullable().optional(),
transactionId: z.string().nullable().optional(),
message: MessageContentSchema,
active: z.boolean(),
display: ComponentSchema,
displayComponentId: z.number().int().nullable().optional(),
});
export const MessageInfoProps = MessageInfoSchema.keyof().enum;
export type MessageInfoDto = z.infer<typeof MessageInfoSchema>;
export const MessageInfoCreateSchema = MessageInfoSchema.omit({
databaseId: true,
tenant: true,
display: true,
updatedAt: true,
createdAt: true,
});
export type MessageInfoCreate = z.infer<typeof MessageInfoCreateSchema>;
export const messageInfoSchemas = {
MessageInfo: MessageInfoSchema,
MessageInfoCreate: MessageInfoCreateSchema,
};

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import { SampledValueSchema } from './types/sampled.value.dto.js';
export const MeterValueSchema = BaseSchema.extend({
id: z.number().int().optional(),
transactionEventId: z.number().int().nullable().optional(),
transactionDatabaseId: z.number().int().nullable().optional(),
sampledValue: z.tuple([SampledValueSchema]).rest(SampledValueSchema),
timestamp: z.iso.datetime(),
connectorId: z.number().int().optional(),
tariffId: z.number().int().nullable().optional(),
transactionId: z.string().nullable().optional(),
});
export const MeterValueProps = MeterValueSchema.keyof().enum;
export type MeterValueDto = z.infer<typeof MeterValueSchema>;
export const MeterValueCreateSchema = MeterValueSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type MeterValueCreate = z.infer<typeof MeterValueCreateSchema>;
export const meterValueSchemas = {
MeterValue: MeterValueSchema,
MeterValueCreate: MeterValueCreateSchema,
};

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import {
MessageOriginSchema,
MessageStateSchema,
OCPPVersionSchema,
} from './types/ocpp.message.js';
export const OCPPMessageWithoutRequestResponseSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
stationId: z.number().int().optional(),
correlationId: z.string().optional(),
origin: MessageOriginSchema,
state: MessageStateSchema,
protocol: OCPPVersionSchema,
action: z.string(),
message: z.any(), // JSONB
timestamp: z.iso.datetime(),
});
const OCPPMessageSchema = OCPPMessageWithoutRequestResponseSchema.extend({
requestMessageId: z.number().int().optional(),
requestMessage: OCPPMessageWithoutRequestResponseSchema.optional(),
responseMessages: OCPPMessageWithoutRequestResponseSchema.array().optional(),
});
export const OCPPMessageProps = OCPPMessageSchema.keyof().enum;
export type OCPPMessageDto = z.infer<typeof OCPPMessageSchema>;
export const OCPPMessageCreateSchema = OCPPMessageSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type OCPPMessageCreate = z.infer<typeof OCPPMessageCreateSchema>;
export const ocppMessageSchemas = {
OCPPMessage: OCPPMessageSchema,
OCPPMessageCreate: OCPPMessageCreateSchema,
};

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { EvseTypeSchema } from './evse.type.dto.js';
import { BaseSchema } from './types/base.dto.js';
export const ReservationSchema = BaseSchema.extend({
databaseId: z.number().int(),
id: z.number().int(),
ocppConnectionName: z.string(),
expiryDateTime: z.iso.datetime(),
connectorType: z.string().nullable().optional(),
reserveStatus: z.string().nullable().optional(),
isActive: z.boolean().default(false),
terminatedByTransaction: z.string().nullable().optional(),
idToken: z.record(z.string(), z.any()),
groupIdToken: z.record(z.string(), z.any()).nullable().optional(),
evseId: z.number().int().nullable().optional(),
evse: EvseTypeSchema.nullable().optional(),
});
export const ReservationProps = ReservationSchema.keyof().enum;
export type ReservationDto = z.infer<typeof ReservationSchema>;
export const ReservationCreateSchema = ReservationSchema.omit({
databaseId: true,
tenant: true,
evse: true,
updatedAt: true,
createdAt: true,
});
export type ReservationCreate = z.infer<typeof ReservationCreateSchema>;
export const reservationSchemas = {
Reservation: ReservationSchema,
ReservationCreate: ReservationCreateSchema,
};

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import { SalesTariffEntrySchema } from './types/sales.tariff.js';
export const SalesTariffSchema = BaseSchema.extend({
databaseId: z.number().int(),
id: z.number().int(),
numEPriceLevels: z.number().int().nullable().optional(),
salesTariffDescription: z.string().nullable().optional(),
salesTariffEntry: z.tuple([SalesTariffEntrySchema]).rest(SalesTariffEntrySchema), // Non-empty array
chargingScheduleDatabaseId: z.number().int(),
});
export const SalesTariffProps = SalesTariffSchema.keyof().enum;
export type SalesTariffDto = z.infer<typeof SalesTariffSchema>;
export const SalesTariffCreateSchema = SalesTariffSchema.omit({
databaseId: true,
tenant: true,
chargingSchedule: true,
updatedAt: true,
createdAt: true,
});
export type SalesTariffCreate = z.infer<typeof SalesTariffCreateSchema>;
export const salesTariffSchemas = {
SalesTariff: SalesTariffSchema,
SalesTariffCreate: SalesTariffCreateSchema,
};

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const SecurityEventSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
type: z.string(),
timestamp: z.iso.datetime(),
techInfo: z.string().nullable().optional(),
});
export const SecurityEventProps = SecurityEventSchema.keyof().enum;
export type SecurityEventDto = z.infer<typeof SecurityEventSchema>;
export const SecurityEventCreateSchema = SecurityEventSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type SecurityEventCreate = z.infer<typeof SecurityEventCreateSchema>;
export const securityEventSchemas = {
SecurityEvent: SecurityEventSchema,
SecurityEventCreate: SecurityEventCreateSchema,
};

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod/v4';
import { BaseSchema } from './types/base.dto.js';
import { LocalListAuthorizationSchema } from './local.list.authorization.dto.js';
export const SendLocalListSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
correlationId: z.string(),
versionNumber: z.number().int(),
updateType: z.string(),
localAuthorizationList: z
.array(z.lazy(() => LocalListAuthorizationSchema))
.nullable()
.optional(),
customData: z.any().nullable().optional(),
});
export type SendLocalListDto = z.infer<typeof SendLocalListSchema>;
export const SendLocalListCreateSchema = SendLocalListSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
localAuthorizationList: true,
});
export const sendLocalListSchemas = {
SendLocalList: SendLocalListSchema,
SendLocalListCreate: SendLocalListCreateSchema,
};

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ChargingStationSchema } from './charging.station.dto.js';
import { BaseSchema } from './types/base.dto.js';
export const ServerNetworkProfileSchema = BaseSchema.extend({
id: z.string(),
host: z.string(),
port: z.number().int(),
pingInterval: z.number().int(),
protocols: z.array(z.string()), // OCPPVersionType[]
messageTimeout: z.number().int(),
securityProfile: z.number().int(),
allowUnknownChargingStations: z.boolean(),
tlsKeyFilePath: z.string().optional(),
tlsCertificateChainFilePath: z.string().optional(),
mtlsCertificateAuthorityKeyFilePath: z.string().optional(),
rootCACertificateFilePath: z.string().optional(),
chargingStations: z.array(ChargingStationSchema).nullable().optional(),
});
export const ServerNetworkProfileProps = ServerNetworkProfileSchema.keyof().enum;
export type ServerNetworkProfileDto = z.infer<typeof ServerNetworkProfileSchema>;
export const ServerNetworkProfileCreateSchema = ServerNetworkProfileSchema.omit({
tenant: true,
chargingStations: true,
updatedAt: true,
createdAt: true,
});
export type ServerNetworkProfileCreate = z.infer<typeof ServerNetworkProfileCreateSchema>;
export const serverNetworkProfileSchemas = {
ServerNetworkProfile: ServerNetworkProfileSchema,
ServerNetworkProfileCreate: ServerNetworkProfileCreateSchema,
};

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ServerNetworkProfileSchema } from './server.network.profile.dto.js';
import { BaseSchema } from './types/base.dto.js';
import {
OCPPInterfaceEnumSchema,
OCPPTransportEnumSchema,
OCPPVersionEnumSchema,
} from './types/enums.js';
export const SetNetworkProfileSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
correlationId: z.string(),
websocketServerConfigId: z.string().optional(),
websocketServerConfig: ServerNetworkProfileSchema.optional(),
configurationSlot: z.number().int(),
ocppVersion: OCPPVersionEnumSchema,
ocppTransport: OCPPTransportEnumSchema,
ocppCsmsUrl: z.string(),
messageTimeout: z.number().int(),
securityProfile: z.number().int(),
ocppInterface: OCPPInterfaceEnumSchema,
apn: z.string().optional(), // Stringified JSON
vpn: z.string().optional(), // Stringified JSON
});
export const SetNetworkProfileProps = SetNetworkProfileSchema.keyof().enum;
export type SetNetworkProfileDto = z.infer<typeof SetNetworkProfileSchema>;
export const SetNetworkProfileCreateSchema = SetNetworkProfileSchema.omit({
id: true,
tenant: true,
websocketServerConfig: true,
updatedAt: true,
createdAt: true,
});
export type SetNetworkProfileCreate = z.infer<typeof SetNetworkProfileCreateSchema>;
export const setNetworkProfileSchemas = {
SetNetworkProfile: SetNetworkProfileSchema,
SetNetworkProfileCreate: SetNetworkProfileCreateSchema,
};

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ConnectorSchema } from './connector.dto.js';
import { BaseSchema } from './types/base.dto.js';
export const StartTransactionSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
meterStart: z.number().int(), // Wh
timestamp: z.iso.datetime(),
reservationId: z.number().int().nullable().optional(),
transactionDatabaseId: z.number().int(),
connectorDatabaseId: z.number().int(),
connector: ConnectorSchema.optional(),
});
export const StartTransactionProps = StartTransactionSchema.keyof().enum;
export type StartTransactionDto = z.infer<typeof StartTransactionSchema>;
export const StartTransactionCreateSchema = StartTransactionSchema.omit({
id: true,
tenant: true,
connector: true,
updatedAt: true,
createdAt: true,
});
export type StartTransactionCreate = z.infer<typeof StartTransactionCreateSchema>;
export const startTransactionSchemas = {
StartTransaction: StartTransactionSchema,
StartTransactionCreate: StartTransactionCreateSchema,
};

View File

@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ChargingStationSchema } from './charging.station.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { ConnectorStatusEnumSchema } from './types/enums.js';
export const StatusNotificationSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
timestamp: z.iso.datetime().nullable().optional(),
connectorStatus: ConnectorStatusEnumSchema,
evseId: z.number().int().nullable().optional(),
connectorId: z.number().int(),
errorCode: z.string().nullable().optional(),
info: z.string().nullable().optional(),
vendorId: z.string().nullable().optional(),
vendorErrorCode: z.string().nullable().optional(),
chargingStation: ChargingStationSchema.optional(),
});
export const StatusNotificationProps = StatusNotificationSchema.keyof().enum;
export type StatusNotificationDto = z.infer<typeof StatusNotificationSchema>;
export const StatusNotificationCreateSchema = StatusNotificationSchema.omit({
id: true,
tenant: true,
chargingStation: true,
updatedAt: true,
createdAt: true,
});
export type StatusNotificationCreate = z.infer<typeof StatusNotificationCreateSchema>;
export const statusNotificationSchemas = {
StatusNotification: StatusNotificationSchema,
StatusNotificationCreate: StatusNotificationCreateSchema,
};

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { MeterValueSchema } from './meter.value.dto.js';
import { BaseSchema } from './types/base.dto.js';
export const StopTransactionSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
transactionDatabaseId: z.number(),
meterStop: z.number().int(),
timestamp: z.iso.datetime(),
reason: z.string().optional(),
meterValues: z.array(MeterValueSchema).optional(),
idTokenValue: z.string().optional(),
idTokenType: z.string().optional(),
});
export const StopTransactionProps = StopTransactionSchema.keyof().enum;
export type StopTransactionDto = z.infer<typeof StopTransactionSchema>;
export const StopTransactionCreateSchema = StopTransactionSchema.omit({
id: true,
tenant: true,
transaction: true,
meterValues: true,
updatedAt: true,
createdAt: true,
});
export type StopTransactionCreate = z.infer<typeof StopTransactionCreateSchema>;
export const stopTransactionSchemas = {
StopTransaction: StopTransactionSchema,
StopTransactionCreate: StopTransactionCreateSchema,
};

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const SubscriptionSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
onConnect: z.boolean().default(false),
onClose: z.boolean().default(false),
onMessage: z.boolean().default(false),
sentMessage: z.boolean().default(false),
messageRegexFilter: z.string().nullable().optional(),
url: z.string(),
});
export const SubscriptionProps = SubscriptionSchema.keyof().enum;
export type SubscriptionDto = z.infer<typeof SubscriptionSchema>;
export const SubscriptionCreateSchema = SubscriptionSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type SubscriptionCreate = z.infer<typeof SubscriptionCreateSchema>;
export const subscriptionSchemas = {
Subscription: SubscriptionSchema,
SubscriptionCreate: SubscriptionCreateSchema,
};

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const TariffSchema = BaseSchema.extend({
id: z.number().int().optional(),
currency: z.string().length(3), // CHAR(3)
pricePerKwh: z.number().min(0), // DECIMAL
pricePerMin: z.number().min(0).nullable().optional(), // DECIMAL
pricePerSession: z.number().min(0).nullable().optional(), // DECIMAL
authorizationAmount: z.number().min(0).nullable().optional(), // DECIMAL
paymentFee: z.number().min(0).nullable().optional(), // DECIMAL
taxRate: z.number().min(0).nullable().optional(), // DECIMAL
tariffAltText: z.record(z.string(), z.any()).nullable().optional(), // JSONB
// OCPP 2.1 TariffType fields
tariffId: z.string().nullable().optional(),
validFrom: z.string().datetime().nullable().optional(),
description: z.array(z.any()).nullable().optional(), // MessageContentType[]
energy: z.any().nullable().optional(), // TariffEnergyType
chargingTime: z.any().nullable().optional(), // TariffTimeType
idleTime: z.any().nullable().optional(), // TariffTimeType
fixedFee: z.any().nullable().optional(), // TariffFixedType
reservationTime: z.any().nullable().optional(), // TariffTimeType
reservationFixed: z.any().nullable().optional(), // TariffFixedType
minCost: z.any().nullable().optional(), // PriceType
maxCost: z.any().nullable().optional(), // PriceType
});
export const TariffProps = TariffSchema.keyof().enum;
export type TariffDto = z.infer<typeof TariffSchema>;
export const TariffCreateSchema = TariffSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type TariffCreate = z.infer<typeof TariffCreateSchema>;
export const tariffSchemas = {
Tariff: TariffSchema,
TariffCreate: TariffCreateSchema,
};

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ServerProfileSchema } from './types/ocpi.registration.js';
export const TenantSchema = z.object({
id: z.number().int().optional(),
name: z.string(),
url: z.string().nullable().optional(),
countryCode: z.string().nullable().optional(),
partyId: z.string().nullable().optional(),
serverProfileOCPI: ServerProfileSchema.nullable().optional(),
isUserTenant: z.boolean().default(false),
maxChargingStations: z.number().int().nullable().optional(),
updatedAt: z.date().optional(),
createdAt: z.date().optional(),
});
export const TenantProps = TenantSchema.keyof().enum;
export type TenantDto = z.infer<typeof TenantSchema>;
export const TenantCreateSchema = TenantSchema.omit({
id: true,
updatedAt: true,
createdAt: true,
});
export type TenantCreate = z.infer<typeof TenantCreateSchema>;
export const TenantUpdateSchema = TenantSchema.partial().omit({
updatedAt: true,
createdAt: true,
});
export type TenantUpdate = z.infer<typeof TenantUpdateSchema>;
export const tenantSchemas = {
Tenant: TenantSchema,
TenantCreate: TenantCreateSchema,
TenantUpdate: TenantUpdateSchema,
};

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod/v4';
import { BaseSchema } from './types/base.dto.js';
import { PartnerProfileSchema } from './types/ocpi.registration.js';
export const TenantPartnerSchema = BaseSchema.extend({
id: z.number().int().optional(),
countryCode: z.string().nullable().optional(),
partyId: z.string().nullable().optional(),
partnerProfileOCPI: PartnerProfileSchema,
});
export const TenantPartnerProps = TenantPartnerSchema.keyof().enum;
export type TenantPartnerDto = z.infer<typeof TenantPartnerSchema>;
export const TenantPartnerCreateSchema = TenantPartnerSchema.omit({
id: true,
tenant: true,
updatedAt: true,
createdAt: true,
});
export type TenantPartnerCreate = z.infer<typeof TenantPartnerCreateSchema>;
export const tenantPartnerSchemas = {
TenantPartner: TenantPartnerSchema,
TenantPartnerCreate: TenantPartnerCreateSchema,
};

View File

@@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { AuthorizationSchema } from './authorization.dto.js';
import { ChargingStationSchema } from './charging.station.dto.js';
import { ConnectorSchema } from './connector.dto.js';
import { EvseSchema } from './evse.dto.js';
import { LocationSchema } from './location.dto.js';
import { MeterValueSchema } from './meter.value.dto.js';
import { StartTransactionSchema } from './start.transaction.dto.js';
import { StopTransactionSchema } from './stop.transaction.dto.js';
import { TariffSchema } from './tariff.dto.js';
import { TransactionEventSchema } from './transaction.event.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { TransactionLimitSchema } from './types/transaction.type.js';
export const TransactionSchema = BaseSchema.extend({
id: z.number().int().optional(),
transactionId: z.string(),
ocppConnectionName: z.string(),
stationId: z.int(),
isActive: z.boolean(),
locationId: z.number().int().optional(),
location: LocationSchema.optional(),
station: ChargingStationSchema,
evseId: z.number().int().optional(),
evse: EvseSchema.nullable().optional(),
connectorId: z.number().int().optional(),
connector: ConnectorSchema.nullable().optional(),
authorizationId: z.number().int().optional(),
authorization: AuthorizationSchema.optional(),
tariffId: z.number().int().optional(),
tariff: TariffSchema.optional(),
transactionEvents: z.array(TransactionEventSchema).optional(),
meterValues: z.array(MeterValueSchema).optional(),
startTransaction: StartTransactionSchema.optional(),
stopTransaction: StopTransactionSchema.optional(),
chargingState: z.string().nullable().optional(),
timeSpentCharging: z.number().int().nullable().optional(), // BIGINT
meterStart: z.number().int().nullable().optional(),
totalKwh: z.number().nullable().optional(), // DECIMAL
stoppedReason: z.string().nullable().optional(),
remoteStartId: z.number().int().nullable().optional(),
totalCost: z.number().optional(), // DECIMAL
startTime: z.iso.datetime().optional(),
endTime: z.iso.datetime().optional(),
transactionLimit: TransactionLimitSchema.optional().nullable(),
customData: z.any().nullable().optional(),
});
export const TransactionProps = TransactionSchema.keyof().enum;
export type TransactionDto = z.infer<typeof TransactionSchema>;
export const TransactionCreateSchema = TransactionSchema.omit({
id: true,
tenant: true,
location: true,
station: true,
evse: true,
connector: true,
authorization: true,
tariff: true,
transactionEvents: true,
meterValues: true,
startTransaction: true,
stopTransaction: true,
updatedAt: true,
createdAt: true,
});
export type TransactionCreate = z.infer<typeof TransactionCreateSchema>;
export const transactionSchemas = {
Transaction: TransactionSchema,
TransactionCreate: TransactionCreateSchema,
};

View File

@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { EvseTypeSchema } from './evse.type.dto.js';
import { MeterValueSchema } from './meter.value.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { TransactionEventEnumSchema, TriggerReasonEnumSchema } from './types/enums.js';
import { TransactionTypeSchema } from './types/transaction.type.js';
export const TransactionEventSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
eventType: TransactionEventEnumSchema,
meterValue: z.tuple([MeterValueSchema]).rest(MeterValueSchema).optional(), // Non-empty array
timestamp: z.iso.datetime(),
triggerReason: TriggerReasonEnumSchema,
seqNo: z.number().int(),
offline: z.boolean().default(false).nullable().optional(),
numberOfPhasesUsed: z.number().int().nullable().optional(),
cableMaxCurrent: z.number().nullable().optional(), // DECIMAL
reservationId: z.number().int().nullable().optional(),
transactionDatabaseId: z.number().int().optional(),
transactionInfo: TransactionTypeSchema.optional(),
evseId: z.number().int().nullable().optional(),
evse: EvseTypeSchema.omit({ tenantId: true }).optional(), // TenantId omitted so that raw OCPP data can be stored
idTokenValue: z.string().nullable().optional(),
idTokenType: z.string().nullable().optional(),
customData: z.any().nullable().optional(),
});
export const TransactionEventProps = TransactionEventSchema.keyof().enum;
export type TransactionEventDto = z.infer<typeof TransactionEventSchema>;
export const TransactionEventCreateSchema = TransactionEventSchema.omit({
id: true,
tenant: true,
meterValue: true,
transaction: true,
evse: true,
updatedAt: true,
createdAt: true,
});
export type TransactionEventCreate = z.infer<typeof TransactionEventCreateSchema>;
export const transactionEventSchemas = {
TransactionEvent: TransactionEventSchema,
TransactionEventCreate: TransactionEventCreateSchema,
};

View File

@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { AuthorizationStatusEnumSchema } from './enums.js';
export const AdditionalInfoSchema = z.object({
id: z.number().int().optional(),
additionalIdToken: z.string(),
type: z.string(),
});
export type AdditionalInfo = z.infer<typeof AdditionalInfoSchema>;
export const RealTimeAuthLastAttemptSchema = z.object({
timestamp: z.iso.datetime(),
result: AuthorizationStatusEnumSchema,
ocppConnectionName: z.string(),
evseId: z.number().nullable().optional(),
connectorId: z.number(),
});
export type RealTimeAuthLastAttempt = z.infer<typeof RealTimeAuthLastAttemptSchema>;

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod/v4';
import { TenantSchema } from '@interfaces/dto/tenant.dto.js';
export const BaseSchema = z.object({
tenantId: z.number().int().optional(),
tenant: TenantSchema.optional(),
updatedAt: z.date().optional(),
createdAt: z.date().optional(),
});
export const BaseProps = BaseSchema.keyof().enum;
export type BaseDto = z.infer<typeof BaseSchema>;

View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
export const ACChargingParametersSchema = z.object({
energyAmount: z.number(),
evMinCurrent: z.number(),
evMaxCurrent: z.number(),
evMaxVoltage: z.number(),
});
export type ACChargingParametersType = z.infer<typeof ACChargingParametersSchema>;
export const DCChargingParametersSchema = z.object({
evMaxCurrent: z.number(),
evMaxVoltage: z.number(),
energyAmount: z.number().nullable().optional(),
evMaxPower: z.number().nullable().optional(),
stateOfCharge: z.number().nullable().optional(),
evEnergyCapacity: z.number().nullable().optional(),
fullSoC: z.number().nullable().optional(),
bulkSoC: z.number().nullable().optional(),
});
export type DCChargingParametersType = z.infer<typeof DCChargingParametersSchema>;

View File

@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { OCPP2_0_1 } from '@ocpp/model/index.js';
export const AttributeEnumSchema = z.enum(OCPP2_0_1.AttributeEnumType);
export const DataEnumSchema = z.enum(OCPP2_0_1.DataEnumType);
export const MutabilityEnumSchema = z.enum(OCPP2_0_1.MutabilityEnumType);

View File

@@ -0,0 +1,716 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
// ============================================================================
// Schemas
// ============================================================================
export const AsyncJobNameSchema = z.enum(['FETCH_OCPI_TOKENS']);
export const AsyncJobActionSchema = z.enum(['RESUME', 'STOP']);
export const AttributeEnumSchema = z.enum(['Actual', 'Target', 'MinSet', 'MaxSet']);
export const AuthorizationStatusEnumSchema = z.enum([
'Accepted',
'Blocked',
'ConcurrentTx',
'Expired',
'Invalid',
'NoCredit',
'NotAllowedTypeEVSE',
'NotAtThisLocation',
'NotAtThisTime',
'Unknown',
]);
export const AuthorizationWhitelistEnumSchema = z.enum(['Never', 'Allowed', 'AllowedOffline']);
export const AuthorizeCertificateStatusEnumSchema = z.enum([
'Accepted',
'SignatureError',
'CertificateExpired',
'CertificateRevoked',
'NoCertificateAvailable',
'CertChainError',
'ContractCancelled',
]);
export const CancelReservationStatusEnumSchema = z.enum(['Accepted', 'Rejected']);
export const ChargingProfileStatusEnumSchema = z.enum(['Accepted', 'Rejected']);
export const ClearChargingProfileStatusEnumSchema = z.enum(['Accepted', 'Unknown']);
export const CertificateSigningUseEnumSchema = z.enum([
'ChargingStationCertificate',
'V2GCertificate',
'V2G20Certificate',
]);
export const CertificateUseEnumSchema = z.enum([
'V2GRootCertificate',
'MORootCertificate',
'CSMSRootCertificate',
'V2GCertificateChain',
'ManufacturerRootCertificate',
'OEMRootCertificate',
]);
export const ChargingLimitSourceEnumSchema = z.enum(['EMS', 'Other', 'SO', 'CSO']);
export const ChargingProfileKindEnumSchema = z.enum(['Absolute', 'Recurring', 'Relative']);
export const ChargingProfilePurposeEnumSchema = z.enum([
'ChargingStationExternalConstraints',
'ChargingStationMaxProfile',
'TxDefaultProfile',
'TxProfile',
]);
export const ChargingRateUnitEnumSchema = z.enum(['W', 'A']);
export const ChargingStateEnumSchema = z.enum([
'Charging',
'EVConnected',
'SuspendedEV',
'SuspendedEVSE',
'Idle',
]);
export const ChargingStationCapabilitySchema = z.enum([
'ChargingProfileCapable',
'ChargingPreferencesCapable',
'ChipCardSupport',
'ContactlessCardSupport',
'CreditCardPayable',
'DebitCardPayable',
'PEDTerminal',
'RemoteStartStopCapable',
'Reservable',
'RFIDReader',
'StartSessionConnectorRequired',
'TokenGroupCapable',
'UnlockCapable',
]);
export const ChargingStationParkingRestrictionSchema = z.enum([
'EVOnly',
'Plugged',
'Disabled',
'Customers',
'Motorcycles',
]);
export const ChargingStationSequenceTypeSchema = z.enum([
'customerInformation',
'getBaseReport',
'getChargingProfiles',
'getDisplayMessages',
'getLog',
'getMonitoringReport',
'getReport',
'publishFirmware',
'remoteStartId',
'updateFirmware',
'transactionId',
]);
export const ClearMessageStatusEnumSchema = z.enum(['Accepted', 'Unknown', 'Rejected']);
export const ConnectorErrorCodeEnumSchema = z.enum([
'ConnectorLockFailure',
'EVCommunicationError',
'GroundFailure',
'HighTemperature',
'InternalError',
'LocalListConflict',
'NoError',
'OtherError',
'OverCurrentFailure',
'PowerMeterFailure',
'PowerSwitchFailure',
'ReaderFailure',
'ResetFailure',
'UnderVoltage',
'OverVoltage',
'WeakSignal',
]);
export const ConnectorFormatEnumSchema = z.enum(['Socket', 'Cable']);
export const ConnectorPowerTypeEnumSchema = z.enum([
'AC1Phase',
'AC2Phase',
'AC2PhaseSplit',
'AC3Phase',
'DC',
]);
export const ConnectorStatusEnumSchema = z.enum([
'Available',
'Occupied',
'Preparing',
'Charging',
'SuspendedEVSE',
'SuspendedEV',
'Finishing',
'Reserved',
'Unavailable',
'Faulted',
'Unknown',
]);
export const ConnectorTypeEnumSchema = z.enum([
'CHAdeMO',
'ChaoJi',
'DomesticA',
'DomesticB',
'DomesticC',
'DomesticD',
'DomesticE',
'DomesticF',
'DomesticG',
'DomesticH',
'DomesticI',
'DomesticJ',
'DomesticK',
'DomesticL',
'DomesticM',
'DomesticN',
'DomesticO',
'GBTAC',
'GBTDC',
'IEC603092Single16',
'IEC603092Three16',
'IEC603092Three32',
'IEC603092Three64',
'IEC62196T1',
'IEC62196T1COMBO',
'IEC62196T2',
'IEC62196T2COMBO',
'IEC62196T3A',
'IEC62196T3C',
'NEMA520',
'NEMA630',
'NEMA650',
'NEMA1030',
'NEMA1050',
'NEMA1430',
'NEMA1450',
'PantographBottomUp',
'PantographTopDown',
'TeslaR',
'TeslaS',
]);
export const DataEnumSchema = z.enum([
'string',
'decimal',
'integer',
'dateTime',
'boolean',
'OptionList',
'SequenceList',
'MemberList',
'passwordString',
]);
export const DataTransferEnumSchema = z.enum([
'Accepted',
'Rejected',
'UnknownMessageId',
'UnknownVendorId',
]);
export const DataTransferStatusSchema = z.enum([
'Accepted',
'Rejected',
'UnknownMessageId',
'UnknownVendorId',
]);
export const DeleteCertificateStatusEnumSchema = z.enum(['Accepted', 'Failed', 'NotFound']);
export const DisplayMessageStatusEnumSchema = z.enum([
'Accepted',
'NotSupportedMessageFormat',
'Rejected',
'NotSupportedPriority',
'NotSupportedState',
'UnknownTransaction',
'LanguageNotSupported',
]);
export const EventNotificationEnumSchema = z.enum([
'HardWiredNotification',
'HardWiredMonitor',
'PreconfiguredMonitor',
'CustomMonitor',
]);
export const EventTriggerEnumSchema = z.enum(['Alerting', 'Delta', 'Periodic']);
export const EnergyTransferModeEnumSchema = z.enum([
'DC',
'AC_single_phase',
'AC_two_phase',
'AC_three_phase',
]);
export const CostKindEnumSchema = z.enum([
'CarbonDioxideEmission',
'RelativePricePercentage',
'RenewableGenerationPercentage',
]);
export const GetCertificateStatusEnumSchema = z.enum(['Accepted', 'Failed']);
export const GenericDeviceModelStatusEnumSchema = z.enum([
'Accepted',
'Rejected',
'NotSupported',
'EmptyResultSet',
]);
export const GenericStatusEnumSchema = z.enum(['Accepted', 'Failed', 'Rejected']);
export const GetInstalledCertificateStatusEnumSchema = z.enum(['Accepted', 'NotFound']);
export const HashAlgorithmEnumSchema = z.enum(['SHA256', 'SHA384', 'SHA512']);
export const IdTokenEnumSchema = z.enum([
'Central',
'DirectPayment',
'eMAID',
'EVCCID',
'ISO14443',
'ISO15693',
'KeyCode',
'Local',
'MacAddress',
'NoAuthorization',
'Other',
'VIN',
]);
export const InstallCertificateStatusEnumSchema = z.enum(['Accepted', 'Rejected', 'Failed']);
export const InstallCertificateUseEnumSchema = z.enum([
'V2GRootCertificate',
'MORootCertificate',
'ManufacturerRootCertificate',
'CSMSRootCertificate',
'OEMRootCertificate',
]);
export const Iso15118EVCertificateStatusEnumSchema = z.enum(['Accepted', 'Failed']);
export const LocationEnumSchema = z.enum(['Body', 'Cable', 'EV', 'Inlet', 'Outlet']);
export const LocationFacilityEnumSchema = z.enum([
'Hotel',
'Restaurant',
'Cafe',
'Mall',
'Supermarket',
'Sport',
'RecreationArea',
'Nature',
'Museum',
'BikeSharing',
'BusStop',
'TaxiStand',
'TramStop',
'MetroStation',
'TrainStation',
'Airport',
'ParkingLot',
'CarpoolParking',
'FuelStation',
'Wifi',
]);
export const LocationParkingEnumSchema = z.enum([
'AlongMotorway',
'ParkingGarage',
'ParkingLot',
'OnDriveway',
'OnStreet',
'UndergroundGarage',
]);
export const MessageFormatEnumSchema = z.enum(['ASCII', 'HTML', 'URI', 'UTF8']);
export const MonitorEnumSchema = z.enum([
'UpperThreshold',
'LowerThreshold',
'Delta',
'Periodic',
'PeriodicClockAligned',
'TargetDelta',
'TargetDeltaRelative',
]);
export const MessagePriorityEnumSchema = z.enum(['AlwaysFront', 'InFront', 'NormalCycle']);
export const MessageStateEnumSchema = z.enum(['Charging', 'Faulted', 'Idle', 'Unavailable']);
export const MutabilityEnumSchema = z.enum(['ReadOnly', 'WriteOnly', 'ReadWrite']);
export const MeasurandEnumSchema = z.enum([
'Current.Export',
'Current.Import',
'Current.Offered',
'Energy.Active.Export.Register',
'Energy.Active.Import.Register',
'Energy.Reactive.Export.Register',
'Energy.Reactive.Import.Register',
'Energy.Active.Export.Interval',
'Energy.Active.Import.Interval',
'Energy.Active.Net',
'Energy.Reactive.Export.Interval',
'Energy.Reactive.Import.Interval',
'Energy.Reactive.Net',
'Energy.Apparent.Net',
'Energy.Apparent.Import',
'Energy.Apparent.Export',
'Frequency',
'Power.Active.Export',
'Power.Active.Import',
'Power.Factor',
'Power.Offered',
'Power.Reactive.Export',
'Power.Reactive.Import',
'RPM',
'SoC',
'Temperature',
'Voltage',
]);
export const NotifyEVChargingNeedsStatusEnumSchema = z.enum([
'Accepted',
'Rejected',
'Processing',
'NoChargingProfile',
]);
export const MonitoringCriterionEnumSchema = z.enum([
'ThresholdMonitoring',
'DeltaMonitoring',
'PeriodicMonitoring',
]);
export const OCPIVersionNumberSchema = z.enum(['2.2.1']);
export const OCPPInterfaceEnumSchema = z.enum([
'Wired0',
'Wired1',
'Wired2',
'Wired3',
'Wireless0',
'Wireless1',
'Wireless2',
'Wireless3',
]);
export const OCPPTransportEnumSchema = z.enum(['JSON', 'SOAP']);
export const OCPPVersionEnumSchema = z.enum(['OCPP12', 'OCPP15', 'OCPP16', 'OCPP20']);
export const PhaseEnumSchema = z.enum([
'L1',
'L2',
'L3',
'N',
'L1-N',
'L2-N',
'L3-N',
'L1-L2',
'L2-L3',
'L3-L1',
]);
export const ReadingContextEnumSchema = z.enum([
'Interruption.Begin',
'Interruption.End',
'Other',
'Sample.Clock',
'Sample.Periodic',
'Transaction.Begin',
'Transaction.End',
'Trigger',
]);
export const ReasonEnumSchema = z.enum([
'DeAuthorized',
'EmergencyStop',
'EnergyLimitReached',
'EVDisconnected',
'GroundFault',
'ImmediateReset',
'Local',
'LocalOutOfCredit',
'MasterPass',
'Other',
'OvercurrentFault',
'PowerLoss',
'PowerQuality',
'Reboot',
'Remote',
'SOCLimitReached',
'StoppedByEV',
'TimeLimitReached',
'Timeout',
]);
export const RecurrencyKindEnumSchema = z.enum(['Daily', 'Weekly']);
export const RegistrationStatusEnumSchema = z.enum(['Accepted', 'Pending', 'Rejected']);
export const RequestStartStopStatusEnumSchema = z.enum(['Accepted', 'Rejected']);
export const ReservationUpdateStatusEnumSchema = z.enum(['Expired', 'Removed', 'NoTransaction']);
export const ReserveNowStatusEnumSchema = z.enum([
'Accepted',
'Faulted',
'Occupied',
'Rejected',
'Unavailable',
]);
export const ResetEnumSchema = z.enum(['Immediate', 'OnIdle', 'ImmediateAndResume']);
export const SendLocalListStatusEnumSchema = z.enum(['Accepted', 'Failed', 'VersionMismatch']);
export const SetMonitoringStatusEnumSchema = z.enum([
'Accepted',
'UnknownComponent',
'UnknownVariable',
'UnsupportedMonitorType',
'Rejected',
'Duplicate',
]);
export const SetNetworkProfileStatusEnumSchema = z.enum(['Accepted', 'Rejected', 'Failed']);
export const SetVariableStatusEnumSchema = z.enum([
'Accepted',
'Rejected',
'UnknownComponent',
'UnknownVariable',
'NotSupportedAttributeType',
'RebootRequired',
]);
export const TransactionEventEnumSchema = z.enum(['Ended', 'Started', 'Updated']);
export const TriggerReasonEnumSchema = z.enum([
'Authorized',
'CablePluggedIn',
'ChargingRateChanged',
'ChargingStateChanged',
'Deauthorized',
'EnergyLimitReached',
'EVCommunicationLost',
'EVConnectTimeout',
'MeterValueClock',
'MeterValuePeriodic',
'TimeLimitReached',
'Trigger',
'UnlockCommand',
'StopAuthorized',
'EVDeparted',
'EVDetected',
'RemoteStop',
'RemoteStart',
'AbnormalCondition',
'SignedDataReceived',
'ResetCommand',
]);
export const TariffSetStatusEnumSchema = z.enum([
'Accepted',
'Rejected',
'TooManyElements',
'ConditionNotSupported',
'DuplicateTariffId',
]);
export const UpdateEnumSchema = z.enum(['Differential', 'Full']);
// ============================================================================
// Enum Exports
// ============================================================================
export const AsyncJobNameEnum = AsyncJobNameSchema.enum;
export const AsyncJobActionEnum = AsyncJobActionSchema.enum;
export const AttributeEnum = AttributeEnumSchema.enum;
export const AuthorizationStatusEnum = AuthorizationStatusEnumSchema.enum;
export const AuthorizationWhitelistEnum = AuthorizationWhitelistEnumSchema.enum;
export const AuthorizeCertificateStatusEnum = AuthorizeCertificateStatusEnumSchema.enum;
export const CancelReservationStatusEnum = CancelReservationStatusEnumSchema.enum;
export const CertificateSigningUseEnum = CertificateSigningUseEnumSchema.enum;
export const ChargingProfileStatusEnum = ChargingProfileStatusEnumSchema.enum;
export const ClearChargingProfileStatusEnum = ClearChargingProfileStatusEnumSchema.enum;
export const CertificateUseEnum = CertificateUseEnumSchema.enum;
export const ChargingStateEnum = ChargingStateEnumSchema.enum;
export const ChargingStationCapabilityEnum = ChargingStationCapabilitySchema.enum;
export const ChargingStationParkingRestrictionEnum = ChargingStationParkingRestrictionSchema.enum;
export const ChargingStationSequenceTypeEnum = ChargingStationSequenceTypeSchema.enum;
export const ClearMessageStatusEnum = ClearMessageStatusEnumSchema.enum;
export const ConnectorErrorCodeEnum = ConnectorErrorCodeEnumSchema.enum;
export const ConnectorFormatEnum = ConnectorFormatEnumSchema.enum;
export const ConnectorPowerTypeEnum = ConnectorPowerTypeEnumSchema.enum;
export const ConnectorStatusEnum = ConnectorStatusEnumSchema.enum;
export const ConnectorTypeEnum = ConnectorTypeEnumSchema.enum;
export const ChargingProfileKindEnum = ChargingProfileKindEnumSchema.enum;
export const ChargingProfilePurposeEnum = ChargingProfilePurposeEnumSchema.enum;
export const ChargingRateUnitEnum = ChargingRateUnitEnumSchema.enum;
export const ChargingLimitSourceEnum = ChargingLimitSourceEnumSchema.enum;
export const CostKindEnum = CostKindEnumSchema.enum;
export const DataEnum = DataEnumSchema.enum;
export const DataTransferEnum = DataTransferEnumSchema.enum;
export const DataTransferStatusEnum = DataTransferStatusSchema.enum;
export const DeleteCertificateStatusEnum = DeleteCertificateStatusEnumSchema.enum;
export const DisplayMessageStatusEnum = DisplayMessageStatusEnumSchema.enum;
export const EnergyTransferModeEnum = EnergyTransferModeEnumSchema.enum;
export const EventNotificationEnum = EventNotificationEnumSchema.enum;
export const EventTriggerEnum = EventTriggerEnumSchema.enum;
export const GenericDeviceModelStatusEnum = GenericDeviceModelStatusEnumSchema.enum;
export const GetCertificateStatusEnum = GetCertificateStatusEnumSchema.enum;
export const GenericStatusEnum = GenericStatusEnumSchema.enum;
export const GetInstalledCertificateStatusEnum = GetInstalledCertificateStatusEnumSchema.enum;
export const HashAlgorithmEnum = HashAlgorithmEnumSchema.enum;
export const IdTokenEnum = IdTokenEnumSchema.enum;
export const InstallCertificateStatusEnum = InstallCertificateStatusEnumSchema.enum;
export const InstallCertificateUseEnum = InstallCertificateUseEnumSchema.enum;
export const Iso15118EVCertificateStatusEnum = Iso15118EVCertificateStatusEnumSchema.enum;
export const LocationEnum = LocationEnumSchema.enum;
export const LocationFacilityEnum = LocationFacilityEnumSchema.enum;
export const LocationParkingEnum = LocationParkingEnumSchema.enum;
export const MeasurandEnum = MeasurandEnumSchema.enum;
export const MessageFormatEnum = MessageFormatEnumSchema.enum;
export const MutabilityEnum = MutabilityEnumSchema.enum;
export const MonitorEnum = MonitorEnumSchema.enum;
export const MessagePriorityEnum = MessagePriorityEnumSchema.enum;
export const MessageStateEnum = MessageStateEnumSchema.enum;
export const MonitoringCriterionEnum = MonitoringCriterionEnumSchema.enum;
export const NotifyEVChargingNeedsStatusEnum = NotifyEVChargingNeedsStatusEnumSchema.enum;
export const OCPIVersionNumberEnum = OCPIVersionNumberSchema.enum;
export const OCPPInterfaceEnum = OCPPInterfaceEnumSchema.enum;
export const OCPPTransportEnum = OCPPTransportEnumSchema.enum;
export const OCPPVersionEnum = OCPPVersionEnumSchema.enum;
export const PhaseEnum = PhaseEnumSchema.enum;
export const ReadingContextEnum = ReadingContextEnumSchema.enum;
export const RecurrencyKindEnum = RecurrencyKindEnumSchema.enum;
export const ReasonEnum = ReasonEnumSchema.enum;
export const RegistrationStatusEnum = RegistrationStatusEnumSchema.enum;
export const RequestStartStopStatusEnum = RequestStartStopStatusEnumSchema.enum;
export const ReservationUpdateStatusEnum = ReservationUpdateStatusEnumSchema.enum;
export const ReserveNowStatusEnum = ReserveNowStatusEnumSchema.enum;
export const ResetEnum = ResetEnumSchema.enum;
export const SendLocalListStatusEnum = SendLocalListStatusEnumSchema.enum;
export const SetMonitoringStatusEnum = SetMonitoringStatusEnumSchema.enum;
export const SetNetworkProfileStatusEnum = SetNetworkProfileStatusEnumSchema.enum;
export const SetVariableStatusEnum = SetVariableStatusEnumSchema.enum;
export const TariffSetStatusEnum = TariffSetStatusEnumSchema.enum;
export const TransactionEventEnum = TransactionEventEnumSchema.enum;
export const TriggerReasonEnum = TriggerReasonEnumSchema.enum;
export const UpdateEnum = UpdateEnumSchema.enum;
// ============================================================================
// Type Exports
// ============================================================================
export type AsyncJobNameEnumType = z.infer<typeof AsyncJobNameSchema>;
export type AsyncJobActionEnumType = z.infer<typeof AsyncJobActionSchema>;
export type AttributeEnumType = z.infer<typeof AttributeEnumSchema>;
export type AuthorizationStatusEnumType = z.infer<typeof AuthorizationStatusEnumSchema>;
export type AuthorizationWhitelistEnumType = z.infer<typeof AuthorizationWhitelistEnumSchema>;
export type AuthorizeCertificateStatusEnumType = z.infer<
typeof AuthorizeCertificateStatusEnumSchema
>;
export type CancelReservationStatusEnumType = z.infer<typeof CancelReservationStatusEnumSchema>;
export type CertificateSigningUseEnumType = z.infer<typeof CertificateSigningUseEnumSchema>;
export type ChargingProfileStatusEnumType = z.infer<typeof ChargingProfileStatusEnumSchema>;
export type ClearChargingProfileStatusEnumType = z.infer<
typeof ClearChargingProfileStatusEnumSchema
>;
export type CertificateUseEnumType = z.infer<typeof CertificateUseEnumSchema>;
export type ChargingStateEnumType = z.infer<typeof ChargingStateEnumSchema>;
export type ChargingStationCapabilityEnumType = z.infer<typeof ChargingStationCapabilitySchema>;
export type ChargingStationParkingRestrictionEnumType = z.infer<
typeof ChargingStationParkingRestrictionSchema
>;
export type ClearMessageStatusEnumType = z.infer<typeof ClearMessageStatusEnumSchema>;
export type ChargingStationSequenceTypeEnumType = z.infer<typeof ChargingStationSequenceTypeSchema>;
export type ConnectorErrorCodeEnumType = z.infer<typeof ConnectorErrorCodeEnumSchema>;
export type ConnectorFormatEnumType = z.infer<typeof ConnectorFormatEnumSchema>;
export type ConnectorPowerTypeEnumType = z.infer<typeof ConnectorPowerTypeEnumSchema>;
export type ConnectorStatusEnumType = z.infer<typeof ConnectorStatusEnumSchema>;
export type ConnectorTypeEnumType = z.infer<typeof ConnectorTypeEnumSchema>;
export type ChargingProfileKindEnumType = z.infer<typeof ChargingProfileKindEnumSchema>;
export type ChargingProfilePurposeEnumType = z.infer<typeof ChargingProfilePurposeEnumSchema>;
export type ChargingRateUnitEnumType = z.infer<typeof ChargingRateUnitEnumSchema>;
export type ChargingLimitSourceEnumType = z.infer<typeof ChargingLimitSourceEnumSchema>;
export type CostKindEnumType = z.infer<typeof CostKindEnumSchema>;
export type DataEnumType = z.infer<typeof DataEnumSchema>;
export type DataTransferEnumType = z.infer<typeof DataTransferEnumSchema>;
export type DataTransferStatusType = z.infer<typeof DataTransferStatusSchema>;
export type DeleteCertificateStatusEnumType = z.infer<typeof DeleteCertificateStatusEnumSchema>;
export type DisplayMessageStatusEnumType = z.infer<typeof DisplayMessageStatusEnumSchema>;
export type EnergyTransferModeEnumType = z.infer<typeof EnergyTransferModeEnumSchema>;
export type EventTriggerEnumType = z.infer<typeof EventTriggerEnumSchema>;
export type EventNotificationEnumType = z.infer<typeof EventNotificationEnumSchema>;
export type GenericDeviceModelStatusEnumType = z.infer<typeof GenericDeviceModelStatusEnumSchema>;
export type GenericStatusEnumType = z.infer<typeof GenericStatusEnumSchema>;
export type GetCertificateStatusEnumType = z.infer<typeof GetCertificateStatusEnumSchema>;
export type GetInstalledCertificateStatusEnumType = z.infer<
typeof GetInstalledCertificateStatusEnumSchema
>;
export type HashAlgorithmEnumType = z.infer<typeof HashAlgorithmEnumSchema>;
export type IdTokenEnumType = z.infer<typeof IdTokenEnumSchema>;
export type InstallCertificateStatusEnumType = z.infer<typeof InstallCertificateStatusEnumSchema>;
export type InstallCertificateUseEnumType = z.infer<typeof InstallCertificateUseEnumSchema>;
export type Iso15118EVCertificateStatusEnumType = z.infer<
typeof Iso15118EVCertificateStatusEnumSchema
>;
export type LocationEnumType = z.infer<typeof LocationEnumSchema>;
export type LocationFacilityEnumType = z.infer<typeof LocationFacilityEnumSchema>;
export type LocationParkingEnumType = z.infer<typeof LocationParkingEnumSchema>;
export type MeasurandEnumType = z.infer<typeof MeasurandEnumSchema>;
export type MessageFormatEnumType = z.infer<typeof MessageFormatEnumSchema>;
export type MutabilityEnumType = z.infer<typeof MutabilityEnumSchema>;
export type MonitorEnumType = z.infer<typeof MonitorEnumSchema>;
export type MessagePriorityEnumType = z.infer<typeof MessagePriorityEnumSchema>;
export type MessageStateEnumType = z.infer<typeof MessageStateEnumSchema>;
export type MonitoringCriterionEnumType = z.infer<typeof MonitoringCriterionEnumSchema>;
export type NotifyEVChargingNeedsStatusEnumType = z.infer<
typeof NotifyEVChargingNeedsStatusEnumSchema
>;
export type OCPIVersionNumberEnumType = z.infer<typeof OCPIVersionNumberSchema>;
export type OCPPInterfaceEnumType = z.infer<typeof OCPPInterfaceEnumSchema>;
export type OCPPTransportEnumType = z.infer<typeof OCPPTransportEnumSchema>;
export type OCPPVersionEnumType = z.infer<typeof OCPPVersionEnumSchema>;
export type PhaseEnumType = z.infer<typeof PhaseEnumSchema>;
export type ReadingContextEnumType = z.infer<typeof ReadingContextEnumSchema>;
export type ReasonEnumType = z.infer<typeof ReasonEnumSchema>;
export type RecurrencyKindEnumType = z.infer<typeof RecurrencyKindEnumSchema>;
export type RegistrationStatusEnumType = z.infer<typeof RegistrationStatusEnumSchema>;
export type RequestStartStopStatusEnumType = z.infer<typeof RequestStartStopStatusEnumSchema>;
export type ReservationUpdateStatusEnumType = z.infer<typeof ReservationUpdateStatusEnumSchema>;
export type ReserveNowStatusEnumType = z.infer<typeof ReserveNowStatusEnumSchema>;
export type ResetEnumType = z.infer<typeof ResetEnumSchema>;
export type SendLocalListStatusEnumType = z.infer<typeof SendLocalListStatusEnumSchema>;
export type SetMonitoringStatusEnumType = z.infer<typeof SetMonitoringStatusEnumSchema>;
export type SetNetworkProfileStatusEnumType = z.infer<typeof SetNetworkProfileStatusEnumSchema>;
export type SetVariableStatusEnumType = z.infer<typeof SetVariableStatusEnumSchema>;
export type TariffSetStatusEnumType = z.infer<typeof TariffSetStatusEnumSchema>;
export type TransactionEventEnumType = z.infer<typeof TransactionEventEnumSchema>;
export type TriggerReasonEnumType = z.infer<typeof TriggerReasonEnumSchema>;
export type UpdateEnumType = z.infer<typeof UpdateEnumSchema>;

View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export class LocationHours {
regularHours?: LocationRegularHours[] | null;
twentyfourSeven!: boolean;
exceptionalOpenings?: LocationExceptionalPeriod[] | null;
exceptionalClosings?: LocationExceptionalPeriod[] | null;
}
export class LocationRegularHours {
weekday!: number;
periodBegin!: string;
periodEnd!: string;
}
export class LocationExceptionalPeriod {
periodBegin!: Date;
periodEnd!: Date;
}

View File

@@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
export const PointSchema = z.object({
type: z.literal('Point'),
coordinates: z.array(z.number()), // [longitude, latitude], doesn't restrict to 2 elements bc Point type doesn't
});
export type Point = z.infer<typeof PointSchema>;
export const LocationHoursSchema = z.any();
export const StatusInfoSchema = z.object({
reasonCode: z.string(),
additionalInfo: z.string().nullable().optional(),
customData: z.any().nullable().optional(),
});
export type StatusInfo = z.infer<typeof StatusInfoSchema>;

View File

@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { MessageFormatEnumSchema } from './enums.js';
export const MessageContentSchema = z.object({
format: MessageFormatEnumSchema,
language: z.string().nullable().optional(),
content: z.string(),
customData: z.any().nullable().optional(),
});
export type MessageContent = z.infer<typeof MessageContentSchema>;

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { OCPIVersionNumberSchema } from './enums.js';
export const ImageSchema = z.object({
url: z.string(),
type: z.string(),
category: z.string(),
width: z.number().int().optional(),
height: z.number().int().optional(),
});
export const BusinessDetailsSchema = z.object({
name: z.string(),
website: z.string().optional(),
logo: ImageSchema.optional(),
});
export const CredentialRoleSchema = z.object({
role: z.enum(['CPO', 'EMSP', 'HUB', 'NAP', 'NSP', 'SCSP']),
businessDetails: BusinessDetailsSchema,
});
export const VersionSchema = z.object({
version: OCPIVersionNumberSchema,
versionDetailsUrl: z.string().optional(),
});
export const EndpointSchema = z.object({
identifier: z.string(),
url: z.string(),
});
export const CredentialsSchema = z.object({
versionsUrl: z.string(),
token: z.string().optional(),
certificateRef: z.string().optional(),
});
export const ServerProfileSchema = z.object({
credentialsRole: CredentialRoleSchema,
versionDetails: z.array(VersionSchema),
versionEndpoints: z.record(z.string(), z.array(EndpointSchema)),
});
export const PartnerProfileSchema = z.object({
version: VersionSchema,
serverCredentials: CredentialsSchema,
roles: z.array(CredentialRoleSchema).optional(),
credentials: CredentialsSchema.optional(),
endpoints: z.array(EndpointSchema).optional(),
});
export type Image = z.infer<typeof ImageSchema>;
export type BusinessDetails = z.infer<typeof BusinessDetailsSchema>;
export type CredentialRole = z.infer<typeof CredentialRoleSchema>;
export type Version = z.infer<typeof VersionSchema>;
export type Endpoint = z.infer<typeof EndpointSchema>;
export type Credentials = z.infer<typeof CredentialsSchema>;
export type ServerProfile = z.infer<typeof ServerProfileSchema>;
export type PartnerProfile = z.infer<typeof PartnerProfileSchema>;

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { OCPP_CallAction, OCPPVersion } from '@ocpp/rpc/message.js';
import { MessageOrigin, MessageState } from '@interfaces/messages/internal-types.js';
export const CallActionSchema = z.enum(OCPP_CallAction);
export const MessageOriginSchema = z.enum(MessageOrigin);
export const MessageStateSchema = z.enum(MessageState);
export const OCPPVersionSchema = z.enum(OCPPVersion);
export type CallActionEnumType = z.infer<typeof CallActionSchema>;
export type MessageOriginEnumType = z.infer<typeof MessageOriginSchema>;
export type MessageStateEnumType = z.infer<typeof MessageStateSchema>;
export type OCPPVersionEnumType = z.infer<typeof OCPPVersionSchema>;

View File

@@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { CostKindEnumSchema } from './enums.js';
export const RelativeTimeIntervalSchema = z.object({
start: z.number().int(),
duration: z.number().int().nullable().optional(),
customData: z.any().nullable().optional(),
});
export const CostSchema = z.object({
costKind: CostKindEnumSchema,
amount: z.number(),
amountMultiplier: z.number().int().nullable().optional(),
customData: z.any().nullable().optional(),
});
export const ConsumptionCostSchema = z.object({
startValue: z.number(),
cost: z.union([
z.tuple([CostSchema]),
z.tuple([CostSchema, CostSchema]),
z.tuple([CostSchema, CostSchema, CostSchema]),
]),
customData: z.any().nullable().optional(),
});
export const SalesTariffEntrySchema = z.object({
relativeTimeInterval: RelativeTimeIntervalSchema,
ePriceLevel: z.number().int().nullable().optional(),
consumptionCost: z
.union([
z.tuple([ConsumptionCostSchema]),
z.tuple([ConsumptionCostSchema, ConsumptionCostSchema]),
z.tuple([ConsumptionCostSchema, ConsumptionCostSchema, ConsumptionCostSchema]),
])
.nullable()
.optional(),
customData: z.any().nullable().optional(),
});
export type SalesTariffEntry = z.infer<typeof SalesTariffEntrySchema>;
export type RelativeTimeInterval = z.infer<typeof RelativeTimeIntervalSchema>;
export type Cost = z.infer<typeof CostSchema>;
export type ConsumptionCost = z.infer<typeof ConsumptionCostSchema>;

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import {
LocationEnumSchema,
MeasurandEnumSchema,
PhaseEnumSchema,
ReadingContextEnumSchema,
} from './enums.js';
export const UnitOfMeasureSchema = z.object({
unit: z.string().nullable().optional(),
multiplier: z.number().nullable().optional(),
});
export const SignedMeterValueSchema = z.object({
signedMeterData: z.string(),
signingMethod: z.string(),
encodingMethod: z.string(),
publicKey: z.string(),
});
export const SampledValueSchema = z.object({
value: z.number(),
context: ReadingContextEnumSchema.nullable().optional(),
measurand: MeasurandEnumSchema.nullable().optional(),
phase: PhaseEnumSchema.nullable().optional(),
location: LocationEnumSchema.nullable().optional(),
signedMeterValue: SignedMeterValueSchema.nullable().optional(),
unitOfMeasure: UnitOfMeasureSchema.nullable().optional(),
});
export type SampledValue = z.infer<typeof SampledValueSchema>;
export type UnitOfMeasure = z.infer<typeof UnitOfMeasureSchema>;
export type SignedMeterValue = z.infer<typeof SignedMeterValueSchema>;

View File

@@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod/v4';
import { MessageFormatEnumSchema } from './enums.js';
const customData = z.any().nullable().optional();
export const TaxRateSchema = z.object({
type: z.string(),
tax: z.number(),
stack: z.number().int().nullable().optional(),
customData,
});
const taxRates = TaxRateSchema.array().min(1).max(5).nullable().optional();
export const TariffConditionsSchema = z.object({
startTimeOfDay: z.string().nullable().optional(),
endTimeOfDay: z.string().nullable().optional(),
dayOfWeek: z.string().array().min(1).max(7).nullable().optional(),
validFromDate: z.string().nullable().optional(),
validToDate: z.string().nullable().optional(),
evseKind: z.string().nullable().optional(),
minEnergy: z.number().nullable().optional(),
maxEnergy: z.number().nullable().optional(),
minCurrent: z.number().nullable().optional(),
maxCurrent: z.number().nullable().optional(),
minPower: z.number().nullable().optional(),
maxPower: z.number().nullable().optional(),
minTime: z.number().nullable().optional(),
maxTime: z.number().nullable().optional(),
minChargingTime: z.number().nullable().optional(),
maxChargingTime: z.number().nullable().optional(),
minIdleTime: z.number().nullable().optional(),
maxIdleTime: z.number().nullable().optional(),
customData,
});
export const TariffConditionsFixedSchema = z.object({
startTimeOfDay: z.string().nullable().optional(),
endTimeOfDay: z.string().nullable().optional(),
dayOfWeek: z.string().array().min(1).max(7).nullable().optional(),
validFromDate: z.string().nullable().optional(),
validToDate: z.string().nullable().optional(),
evseKind: z.string().nullable().optional(),
paymentBrand: z.string().nullable().optional(),
paymentRecognition: z.string().nullable().optional(),
customData,
});
export const TariffMessageContentSchema = z.object({
format: MessageFormatEnumSchema,
language: z.string().nullable().optional(),
content: z.string(),
customData,
});
export const TariffEnergyPriceSchema = z.object({
priceKwh: z.number(),
conditions: TariffConditionsSchema.nullable().optional(),
customData,
});
export const TariffEnergySchema = z.object({
prices: TariffEnergyPriceSchema.array().min(1),
taxRates,
customData,
});
export const TariffTimePriceSchema = z.object({
priceMinute: z.number(),
conditions: TariffConditionsSchema.nullable().optional(),
customData,
});
export const TariffTimeSchema = z.object({
prices: TariffTimePriceSchema.array().min(1),
taxRates,
customData,
});
export const TariffFixedPriceSchema = z.object({
priceFixed: z.number(),
conditions: TariffConditionsFixedSchema.nullable().optional(),
customData,
});
export const TariffFixedSchema = z.object({
prices: TariffFixedPriceSchema.array().min(1),
taxRates,
customData,
});
export const PriceSchema = z.object({
exclTax: z.number().nullable().optional(),
inclTax: z.number().nullable().optional(),
taxRates,
customData,
});
export type TaxRateType = z.infer<typeof TaxRateSchema>;
export type TariffConditionsType = z.infer<typeof TariffConditionsSchema>;
export type TariffConditionsFixedType = z.infer<typeof TariffConditionsFixedSchema>;
export type MessageContentType = z.infer<typeof TariffMessageContentSchema>;
export type TariffEnergyType = z.infer<typeof TariffEnergySchema>;
export type TariffTimeType = z.infer<typeof TariffTimeSchema>;
export type TariffFixedType = z.infer<typeof TariffFixedSchema>;
export type PriceType = z.infer<typeof PriceSchema>;

View File

@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ChargingStateEnumSchema, ReasonEnumSchema } from './enums.js';
export const TransactionLimitSchema = z.object({
maxCost: z.number().optional().nullable(),
maxEnergy: z.number().optional().nullable(),
maxTime: z.number().int().optional().nullable(),
maxSoC: z.number().int().min(0).max(100).optional().nullable(),
});
export type TransactionLimit = z.infer<typeof TransactionLimitSchema>;
export const TransactionTypeSchema = z.object({
transactionId: z.string(),
chargingState: ChargingStateEnumSchema.nullable().optional(),
timeSpentCharging: z.number().int().nullable().optional(),
stoppedReason: ReasonEnumSchema.nullable().optional(),
remoteStartId: z.number().int().nullable().optional(),
tariffId: z.string().optional().nullable(),
transactionLimit: TransactionLimitSchema.optional().nullable(),
});
export type TransactionType = z.infer<typeof TransactionTypeSchema>;

View File

@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod/v4';
export const AddressSchema = z.object({
name: z.string(),
address1: z.string(),
address2: z.string().nullable().optional(),
city: z.string(),
postalCode: z.string().nullable().optional(),
country: z.string(),
customData: z.any().nullable().optional(),
});
export type AddressType = z.infer<typeof AddressSchema>;

View File

@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ChargingStationSchema } from './charging.station.dto.js';
import { ComponentSchema } from './component.dto.js';
import { EvseTypeSchema } from './evse.type.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { AttributeEnumSchema, DataEnumSchema, MutabilityEnumSchema } from './types/device.model.js';
import { VariableSchema } from './variable.dto.js';
export const VariableAttributeSchema = BaseSchema.extend({
id: z.number().int().optional(),
ocppConnectionName: z.string(),
chargingStation: ChargingStationSchema,
type: AttributeEnumSchema.nullable().optional(),
dataType: DataEnumSchema,
value: z.string().max(4000).nullable().optional(),
mutability: MutabilityEnumSchema.nullable().optional(),
persistent: z.boolean().default(false).nullable().optional(),
constant: z.boolean().default(false).nullable().optional(),
generatedAt: z.iso.datetime(),
variable: VariableSchema,
variableId: z.number().int().nullable().optional(),
component: ComponentSchema,
componentId: z.number().int().nullable().optional(),
evse: EvseTypeSchema.optional(),
evseDatabaseId: z.number().int().nullable().optional(),
bootConfigId: z.string().nullable().optional(),
});
export const VariableAttributeProps = VariableAttributeSchema.keyof().enum;
export type VariableAttributeDto = z.infer<typeof VariableAttributeSchema>;
export const VariableAttributeCreateSchema = VariableAttributeSchema.omit({
id: true,
tenant: true,
chargingStation: true,
variable: true,
component: true,
evse: true,
statuses: true,
bootConfig: true,
updatedAt: true,
createdAt: true,
});
export type VariableAttributeCreate = z.infer<typeof VariableAttributeCreateSchema>;
export const variableAttributeSchemas = {
VariableAttribute: VariableAttributeSchema,
VariableAttributeCreate: VariableAttributeCreateSchema,
};

View File

@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import { DataEnumSchema } from './types/device.model.js';
import { VariableSchema } from './variable.dto.js';
export const VariableCharacteristicsSchema = BaseSchema.extend({
id: z.number().int().optional(),
unit: z.string().nullable().optional(),
dataType: DataEnumSchema,
minLimit: z.number().nullable().optional(), // DECIMAL in DB
maxLimit: z.number().nullable().optional(), // DECIMAL in DB
valuesList: z.string().max(4000).nullable().optional(),
supportsMonitoring: z.boolean(),
variable: VariableSchema,
variableId: z.number().int().nullable().optional(),
});
export const VariableCharacteristicsProps = VariableCharacteristicsSchema.keyof().enum;
export type VariableCharacteristicsDto = z.infer<typeof VariableCharacteristicsSchema>;
export const VariableCharacteristicsCreateSchema = VariableCharacteristicsSchema.omit({
id: true,
tenant: true,
variable: true,
updatedAt: true,
createdAt: true,
});
export type VariableCharacteristicsCreate = z.infer<typeof VariableCharacteristicsCreateSchema>;
export const variableCharacteristicsSchemas = {
VariableCharacteristics: VariableCharacteristicsSchema,
VariableCharacteristicsCreate: VariableCharacteristicsCreateSchema,
};

View File

@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
export const VariableSchema = BaseSchema.extend({
id: z.number().int().optional(),
name: z.string(),
instance: z.string().nullable().optional(),
customData: z.any().nullable().optional(),
});
export const VariableProps = VariableSchema.keyof().enum;
export type VariableDto = z.infer<typeof VariableSchema>;
export const VariableCreateSchema = VariableSchema.omit({
id: true,
tenant: true,
components: true,
variableAttributes: true,
variableCharacteristics: true,
updatedAt: true,
createdAt: true,
});
export type VariableCreate = z.infer<typeof VariableCreateSchema>;
export const variableSchemas = {
Variable: VariableSchema,
VariableCreate: VariableCreateSchema,
};

View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { ComponentSchema } from './component.dto.js';
import { BaseSchema } from './types/base.dto.js';
import { VariableSchema } from './variable.dto.js';
import { EventNotificationEnumSchema, MonitorEnumSchema } from './types/enums.js';
export const VariableMonitoringSchema = BaseSchema.extend({
databaseId: z.number().int(),
id: z.number().int(),
ocppConnectionName: z.string(),
transaction: z.boolean(),
value: z.number().int(),
type: MonitorEnumSchema,
severity: z.number().int(),
variable: VariableSchema,
variableId: z.number().int().nullable().optional(),
component: ComponentSchema,
componentId: z.number().int().nullable().optional(),
eventNotificationType: EventNotificationEnumSchema.nullable().optional(),
});
export const VariableMonitoringProps = VariableMonitoringSchema.keyof().enum;
export type VariableMonitoringDto = z.infer<typeof VariableMonitoringSchema>;
export const VariableMonitoringCreateSchema = VariableMonitoringSchema.omit({
databaseId: true,
tenant: true,
variable: true,
component: true,
updatedAt: true,
createdAt: true,
});
export type VariableMonitoringCreate = z.infer<typeof VariableMonitoringCreateSchema>;
export const variableMonitoringSchemas = {
VariableMonitoring: VariableMonitoringSchema,
VariableMonitoringCreate: VariableMonitoringCreateSchema,
};

View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import { StatusInfoSchema } from './types/location.js';
import { VariableMonitoringSchema } from './variable.monitoring.dto.js';
export const VariableMonitoringStatusSchema = BaseSchema.extend({
id: z.number().int().optional(),
status: z.string(),
statusInfo: StatusInfoSchema.nullable().optional(),
variable: VariableMonitoringSchema,
variableMonitoringId: z.number().int().nullable().optional(),
});
export const VariableMonitoringStatusProps = VariableMonitoringStatusSchema.keyof().enum;
export type VariableMonitoringStatusDto = z.infer<typeof VariableMonitoringStatusSchema>;
export const VariableMonitoringStatusCreateSchema = VariableMonitoringStatusSchema.omit({
id: true,
tenant: true,
variable: true,
updatedAt: true,
createdAt: true,
});
export type VariableMonitoringStatusCreate = z.infer<typeof VariableMonitoringStatusCreateSchema>;
export const variableMonitoringStatusSchemas = {
VariableMonitoringStatus: VariableMonitoringStatusSchema,
VariableMonitoringStatusCreate: VariableMonitoringStatusCreateSchema,
};

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { z } from 'zod';
import { BaseSchema } from './types/base.dto.js';
import { StatusInfoSchema } from './types/location.js';
import { VariableAttributeSchema } from './variable.attribute.dto.js';
export const VariableStatusSchema = BaseSchema.extend({
id: z.number().int().optional(),
value: z.string().max(4000),
status: z.string(),
statusInfo: StatusInfoSchema.nullable().optional(),
variable: VariableAttributeSchema,
variableAttributeId: z.number().int().nullable().optional(),
});
export const VariableStatusProps = VariableStatusSchema.keyof().enum;
export type VariableStatusDto = z.infer<typeof VariableStatusSchema>;
export const VariableStatusCreateSchema = VariableStatusSchema.omit({
id: true,
tenant: true,
variable: true,
updatedAt: true,
createdAt: true,
});
export type VariableStatusCreate = z.infer<typeof VariableStatusCreateSchema>;
export const variableStatusSchemas = {
VariableStatus: VariableStatusSchema,
VariableStatusCreate: VariableStatusCreateSchema,
};

View File

@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export interface IFileAccess {
getFileURL(): string;
}

View File

@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { Buffer } from 'node:buffer';
export interface IFileStorage {
/**
*
* @param fileName Name of the file
* @param content File content
* @param filePath The path of the file, if not in root. Used as the bucket name for S3.
*
* @returns The ID of the file
*/
saveFile(fileName: string, content: Buffer, filePath?: string): Promise<string>;
/**
*
* @param id The ID of the file
* @param filePath The path of the file, if not included in the ID. Used as the bucket name for S3.
*
* @returns The file content
*/
getFile(id: string, filePath?: string): Promise<string | undefined>;
/**
* Checks whether a file or directory exists at the given path.
*
* @param path The file or directory path. For object storage (S3, GCP), treated as an object key or prefix.
*/
exists(path: string): Promise<boolean>;
/**
* Creates a directory at the given path.
* For object storage backends (S3, GCP) this may be a no-op since directories are implicit.
*
* @param path The directory path to create
* @param options Optional options, e.g. { recursive: true }
*/
createDirectory(path: string, options?: { recursive?: boolean }): Promise<void>;
/**
* Removes a file or directory at the given path.
* For object storage backends (S3, GCP), recursive removal deletes all objects sharing the path prefix.
*
* @param path The path to remove
* @param options Optional options, e.g. { recursive: true, force: true }
*/
deleteFile(path: string, options?: { recursive?: boolean; force?: boolean }): Promise<void>;
}

View File

@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
export type { IFileAccess } from './fileAccess.js';
export type { IFileStorage } from './fileStorage.js';

View File

@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import { EventEmitter } from 'events';
import type { ILogObj } from 'tslog';
import { Logger } from 'tslog';
import type { IConnectionManager } from './IConnectionManager.js';
/**
* Abstract base class for managing a message transport connection.
*
* Implementations are responsible for establishing, maintaining, and closing
* connections to a specific transport backend (e.g. RabbitMQ, Kafka).
*
* Emits:
* - `connected` when a connection is established (with the connection object as argument)
* - `disconnected` when the connection is lost
* - `error` on connection errors
*
* @template TConnection The transport-specific connection type returned by {@link connect}.
*/
export abstract class AbstractConnectionManager<TConnection = unknown>
extends EventEmitter
implements IConnectionManager
{
protected _logger: Logger<ILogObj>;
public state: string = 'disconnected';
constructor(logger?: Logger<ILogObj>) {
super();
this._logger = logger
? logger.getSubLogger({ name: this.constructor.name })
: new Logger<ILogObj>({ name: this.constructor.name });
}
/**
* Establishes a connection to the transport backend.
* Implementations should handle in-progress connection attempts and
* return the existing connection if already connected.
*/
abstract connect(): Promise<TConnection>;
/**
* Gracefully closes the connection.
*/
abstract close(): Promise<void>;
/**
* Returns true if there is an active connection.
*/
abstract isConnected(): boolean;
}

View File

@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { ILogObj } from 'tslog';
import { Logger } from 'tslog';
import type { IMessage } from './Message.js';
import type { IMessageHandler } from './MessageHandler.js';
import { OcppError } from '@ocpp/rpc/message.js';
import type { OcppRequest, OcppResponse } from '@ocpp/internal-types.js';
import type { CallAction } from '@ocpp/rpc/message.js';
import type { IModule } from '@interfaces/modules/Module.js';
import type { HandlerProperties } from '@interfaces/messages/internal-types.js';
/**
* Abstract class implementing {@link IMessageHandler}.
*/
export abstract class AbstractMessageHandler implements IMessageHandler {
/**
* Fields
*/
protected _module?: IModule;
protected _logger: Logger<ILogObj>;
/**
* Constructor
*
* @param config The system configuration.
* @param logger [Optional] The logger to use.
*/
constructor(logger?: Logger<ILogObj>, module?: IModule) {
this._module = module;
this._logger = logger
? logger.getSubLogger({ name: this.constructor.name })
: new Logger<ILogObj>({ name: this.constructor.name });
}
/**
* Getter & Setter
*/
get module(): IModule | undefined {
return this._module;
}
set module(value: IModule | undefined) {
this._module = value;
}
/**
* Methods
*/
async handle(
message: IMessage<OcppRequest | OcppResponse | OcppError>,
props?: HandlerProperties,
): Promise<void> {
await this._module?.handle(message, props);
}
/**
* Abstract Methods
*/
abstract subscribe(
identifier: string,
actions?: CallAction[],
filter?: { [k: string]: string },
): Promise<boolean>;
abstract unsubscribe(identifier: string): Promise<boolean>;
abstract shutdown(): Promise<void>;
}

View File

@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2025 Contributors to the CitrineOS Project
//
// SPDX-License-Identifier: Apache-2.0
import type { ILogObj } from 'tslog';
import { Logger } from 'tslog';
export abstract class AbstractMessageSender {
/**
* Fields
*/
protected _logger: Logger<ILogObj>;
/**
* Constructor
*
* @param logger [Optional] The logger to use.
*/
constructor(logger?: Logger<ILogObj>) {
this._logger = logger
? logger.getSubLogger({ name: this.constructor.name })
: new Logger<ILogObj>({ name: this.constructor.name });
}
}

Some files were not shown because too many files have changed in this diff Show More